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/.eslintrc.json b/.eslintrc.json index e1dc514e65e..98084a1071a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,7 +35,8 @@ "external", "status", "origin", - "orientation" + "orientation", + "context" ], // non-complete list of globals that are easy to access unintentionally "no-var": "warn", "jsdoc/no-types": "warn", @@ -108,7 +109,7 @@ "restrictions": [ "vs/nls", "**/vs/base/{common,node}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -145,7 +146,7 @@ "vs/nls", "**/vs/base/{common,node}/**", "**/vs/base/parts/*/{common,node}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -155,7 +156,7 @@ "vs/css!./**/*", "**/vs/base/{common,browser,node,electron-browser}/**", "**/vs/base/parts/*/{common,browser,node,electron-browser}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -164,7 +165,7 @@ "vs/nls", "**/vs/base/{common,node,electron-main}/**", "**/vs/base/parts/*/{common,node,electron-main}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -205,7 +206,7 @@ "**/vs/base/{common,node}/**", "**/vs/base/parts/*/{common,node}/**", "**/vs/platform/*/{common,node}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -216,7 +217,7 @@ "**/vs/base/{common,browser,node}/**", "**/vs/base/parts/*/{common,browser,node,electron-browser}/**", "**/vs/platform/*/{common,browser,node,electron-browser}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -226,7 +227,8 @@ "**/vs/base/{common,node,electron-main}/**", "**/vs/base/parts/*/{common,node,electron-main}/**", "**/vs/platform/*/{common,node,electron-main}/**", - "!path" // node modules (except path where we have our own impl) + "**/vs/code/**", + "*" // node modules ] }, { @@ -395,12 +397,6 @@ "assert" ] }, - { - "target": "**/vs/workbench/workbench.desktop.main.ts", - "restrictions": [ - "**" - ] - }, { "target": "**/vs/workbench/api/common/**", "restrictions": [ @@ -436,7 +432,7 @@ "**/vs/editor/contrib/**", // editor/contrib is equivalent to /browser/ by convention "**/vs/workbench/{common,browser,node,electron-browser,api}/**", "**/vs/workbench/services/*/{common,browser,node,electron-browser}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -451,7 +447,7 @@ "vs/workbench/contrib/files/common/editors/fileEditorInput", "**/vs/workbench/services/**", "**/vs/workbench/test/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -507,7 +503,7 @@ "**/vs/workbench/{common,node}/**", "**/vs/workbench/api/{common,node}/**", "**/vs/workbench/services/**/{common,node}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -520,7 +516,23 @@ "**/vs/editor/**", "**/vs/workbench/{common,browser,node,electron-browser,api}/**", "**/vs/workbench/services/**/{common,browser,node,electron-browser}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules + ] + }, + { + "target": "**/vs/workbench/contrib/**/test/**", + "restrictions": [ + "assert", + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**", + "**/vs/platform/**", + "**/vs/editor/**", + "**/vs/workbench/{common,browser,node,electron-browser}/**", + "**/vs/workbench/services/**", + "**/vs/workbench/contrib/**", + "**/vs/workbench/test/**", + "*" ] }, { @@ -529,21 +541,99 @@ // xterm and its addons are strictly browser-only components "xterm", "xterm-addon-*", - "**/vs/**" + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,browser}/**", + "**/vs/platform/**/{common,browser}/**", + "**/vs/editor/**", + "**/vs/workbench/{common,browser}/**", + "**/vs/workbench/contrib/**/{common,browser}/**", + "**/vs/workbench/services/**/{common,browser}/**" ] }, { "target": "**/vs/workbench/contrib/extensions/browser/**", "restrictions": [ "semver-umd", - "**/vs/**" + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,browser}/**", + "**/vs/platform/**/{common,browser}/**", + "**/vs/editor/**", + "**/vs/workbench/{common,browser}/**", + "**/vs/workbench/contrib/**/{common,browser}/**", + "**/vs/workbench/services/**/{common,browser}/**" ] }, { "target": "**/vs/workbench/contrib/update/browser/update.ts", "restrictions": [ "semver-umd", - "**/vs/**" + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,browser}/**", + "**/vs/platform/**/{common,browser}/**", + "**/vs/editor/**", + "**/vs/workbench/{common,browser}/**", + "**/vs/workbench/contrib/**/{common,browser}/**", + "**/vs/workbench/services/**/{common,browser}/**" + ] + }, + { + "target": "**/vs/workbench/contrib/**/common/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/common/**", + "**/vs/platform/**/common/**", + "**/vs/editor/**", + "**/vs/workbench/common/**", + "**/vs/workbench/api/common/**", + "**/vs/workbench/services/**/common/**", + "**/vs/workbench/contrib/**/common/**" + ] + }, + { + "target": "**/vs/workbench/contrib/**/browser/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,browser}/**", + "**/vs/platform/**/{common,browser}/**", + "**/vs/editor/**", + "**/vs/workbench/{common,browser}/**", + "**/vs/workbench/api/{common,browser}/**", + "**/vs/workbench/services/**/{common,browser}/**", + "**/vs/workbench/contrib/**/{common,browser}/**" + ] + }, + { + "target": "**/vs/workbench/contrib/**/node/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,node}/**", + "**/vs/platform/**/{common,node}/**", + "**/vs/editor/**/common/**", + "**/vs/workbench/{common,node}/**", + "**/vs/workbench/api/{common,node}/**", + "**/vs/workbench/services/**/{common,node}/**", + "**/vs/workbench/contrib/**/{common,node}/**", + "*" // node modules + ] + }, + { + "target": "**/vs/workbench/contrib/**/electron-browser/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,browser,worker,node,electron-browser}/**", + "**/vs/platform/**/{common,browser,node,electron-browser}/**", + "**/vs/editor/**", + "**/vs/workbench/{common,browser,node,electron-browser,api}/**", + "**/vs/workbench/services/**/{common,browser,node,electron-browser}/**", + "**/vs/workbench/contrib/**/{common,browser,node,electron-browser}/**", + "*" // node modules ] }, { @@ -554,7 +644,7 @@ "**/vs/base/parts/**/{common,node}/**", "**/vs/platform/**/{common,node}/**", "**/vs/code/**/{common,node}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -566,7 +656,7 @@ "**/vs/base/parts/**/{common,browser,node,electron-browser}/**", "**/vs/platform/**/{common,browser,node,electron-browser}/**", "**/vs/code/**/{common,browser,node,electron-browser}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -577,7 +667,7 @@ "**/vs/base/parts/**/{common,node,electron-main}/**", "**/vs/platform/**/{common,node,electron-main}/**", "**/vs/code/**/{common,node,electron-main}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, { @@ -590,13 +680,9 @@ "**/vs/workbench/**/{common,node}/**", "**/vs/server/**", "**/vs/code/**/{common,node}/**", - "!path" // node modules (except path where we have our own impl) + "*" // node modules ] }, - { - "target": "**/{node,electron-browser,electron-main}/**", - "restrictions": "**/*" - }, { "target": "**/extensions/**", "restrictions": "**/*" @@ -605,34 +691,73 @@ "target": "**/test/smoke/**", "restrictions": [ "**/test/smoke/**", - "*" + "*" // node modules ] }, { "target": "**/test/automation/**", "restrictions": [ "**/test/automation/**", - "*" + "*" // node modules ] }, { "target": "**/test/integration/**", "restrictions": [ "**/test/integration/**", - "*" + "*" // node modules ] }, { - "target": "{**/api/**.test.ts,}", - "restrictions": "{**/vs/**,assert,sinon,crypto,vscode}" + "target": "**/api/**.test.ts", + "restrictions": [ + "**/vs/**", + "assert", + "sinon", + "crypto", + "vscode" + ] }, { - "target": "{**/**.test.ts,**/test/**}", - "restrictions": "{**/vs/**,assert,sinon,crypto,xterm*}" + "target": "**/{node,electron-browser,electron-main}/**/*.test.ts", + "restrictions": [ + "**/vs/**", + "*" // node modules + ] }, { - "target": "**/{common,browser,workbench}/**", - "restrictions": "**/vs/**" + "target": "**/{node,electron-browser,electron-main}/**/test/**", + "restrictions": [ + "**/vs/**", + "*" // node modules + ] + }, + { + "target": "**/test/{node,electron-browser,electron-main}/**", + "restrictions": [ + "**/vs/**", + "*" // node modules + ] + }, + { + "target": "**/**.test.ts", + "restrictions": [ + "**/vs/**", + "assert", + "sinon", + "crypto", + "xterm*" + ] + }, + { + "target": "**/test/**", + "restrictions": [ + "**/vs/**", + "assert", + "sinon", + "crypto", + "xterm*" + ] } ] }, @@ -669,6 +794,7 @@ "create", "delete", "dispose", + "edit", "end", "expand", "hide", 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/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..1ac8dddb2f9 --- /dev/null +++ b/.github/workflows/author-verified.yml @@ -0,0 +1,36 @@ +name: Author Verified +on: + repository_dispatch: + schedule: + - cron: 20 14 * * * # 4:20pm Zurich + issues: + types: [closed] + +# also make changes in ./on-label.yml +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: 'microsoft/vscode-github-triage-actions' + ref: v3 + path: ./actions + - name: Install Actions + if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested') + run: npm install --production --prefix ./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/ci.yml b/.github/workflows/ci.yml index 18d4d467786..d509cce2a33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,113 @@ on: - release/* jobs: - linux: + # linux: + # runs-on: ubuntu-latest + # env: + # CHILD_CONCURRENCY: "1" + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # steps: + # - uses: actions/checkout@v1 + # # TODO: rename azure-pipelines/linux/xvfb.init to github-actions + # - run: | + # sudo apt-get update + # sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + # sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + # sudo chmod +x /etc/init.d/xvfb + # sudo update-rc.d xvfb defaults + # sudo service xvfb start + # name: Setup Build Environment + # - uses: actions/setup-node@v1 + # with: + # node-version: 10 + # # TODO: cache node modules + # - run: yarn --frozen-lockfile + # name: Install Dependencies + # - run: yarn electron x64 + # name: Download Electron + # - run: yarn gulp hygiene + # name: Run Hygiene Checks + # - run: yarn monaco-compile-check + # name: Run Monaco Editor Checks + # - run: yarn valid-layers-check + # name: Run Valid Layers Checks + # - run: yarn compile + # name: Compile Sources + # - run: yarn download-builtin-extensions + # name: Download Built-in Extensions + # - run: DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" + # name: Run Unit Tests (Electron) + # - run: DISPLAY=:10 yarn test-browser --browser chromium + # name: Run Unit Tests (Browser) + # - run: DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" + # name: Run Integration Tests (Electron) + + # windows: + # runs-on: windows-2016 + # env: + # CHILD_CONCURRENCY: "1" + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # steps: + # - uses: actions/checkout@v1 + # - uses: actions/setup-node@v1 + # with: + # node-version: 10 + # - uses: actions/setup-python@v1 + # with: + # python-version: '2.x' + # - run: yarn --frozen-lockfile + # name: Install Dependencies + # - run: yarn electron + # name: Download Electron + # - run: yarn gulp hygiene + # name: Run Hygiene Checks + # - run: yarn monaco-compile-check + # name: Run Monaco Editor Checks + # - run: yarn valid-layers-check + # name: Run Valid Layers Checks + # - run: yarn compile + # name: Compile Sources + # - run: yarn download-builtin-extensions + # name: Download Built-in Extensions + # - run: .\scripts\test.bat --tfs "Unit Tests" + # name: Run Unit Tests (Electron) + # - run: yarn test-browser --browser chromium + # name: Run Unit Tests (Browser) + # - run: .\scripts\test-integration.bat --tfs "Integration Tests" + # name: Run Integration Tests (Electron) + + # darwin: + # runs-on: macos-latest + # env: + # CHILD_CONCURRENCY: "1" + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # steps: + # - uses: actions/checkout@v1 + # - uses: actions/setup-node@v1 + # with: + # node-version: 10 + # - run: yarn --frozen-lockfile + # name: Install Dependencies + # - run: yarn electron x64 + # name: Download Electron + # - run: yarn gulp hygiene + # name: Run Hygiene Checks + # - run: yarn monaco-compile-check + # name: Run Monaco Editor Checks + # - run: yarn valid-layers-check + # name: Run Valid Layers Checks + # - run: yarn compile + # name: Compile Sources + # - run: yarn download-builtin-extensions + # name: Download Built-in Extensions + # - run: ./scripts/test.sh --tfs "Unit Tests" + # name: Run Unit Tests (Electron) + # - run: yarn test-browser --browser chromium --browser webkit + # name: Run Unit Tests (Browser) + # - run: ./scripts/test-integration.sh --tfs "Integration Tests" + # name: Run Integration Tests (Electron) + + monaco: runs-on: ubuntu-latest env: CHILD_CONCURRENCY: "1" @@ -30,89 +136,9 @@ jobs: - uses: actions/setup-node@v1 with: node-version: 10 - # TODO: cache node modules - run: yarn --frozen-lockfile name: Install Dependencies - - run: yarn electron x64 - name: Download Electron - - run: yarn gulp hygiene - name: Run Hygiene Checks - run: yarn monaco-compile-check name: Run Monaco Editor Checks - - run: yarn valid-layers-check - name: Run Valid Layers Checks - - run: yarn compile - name: Compile Sources - - run: yarn download-builtin-extensions - name: Download Built-in Extensions - - run: DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" - name: Run Unit Tests (Electron) - - run: DISPLAY=:10 yarn test-browser --browser chromium - name: Run Unit Tests (Browser) - - run: DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" - name: Run Integration Tests (Electron) - - windows: - runs-on: windows-2016 - env: - CHILD_CONCURRENCY: "1" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: 10 - - uses: actions/setup-python@v1 - with: - python-version: '2.x' - - run: yarn --frozen-lockfile - name: Install Dependencies - - run: yarn electron - name: Download Electron - - run: yarn gulp hygiene - name: Run Hygiene Checks - - run: yarn monaco-compile-check - name: Run Monaco Editor Checks - - run: yarn valid-layers-check - name: Run Valid Layers Checks - - run: yarn compile - name: Compile Sources - - run: yarn download-builtin-extensions - name: Download Built-in Extensions - - run: .\scripts\test.bat --tfs "Unit Tests" - name: Run Unit Tests (Electron) - - run: yarn test-browser --browser chromium - name: Run Unit Tests (Browser) - - run: .\scripts\test-integration.bat --tfs "Integration Tests" - name: Run Integration Tests (Electron) - - darwin: - runs-on: macos-latest - env: - CHILD_CONCURRENCY: "1" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: 10 - - run: yarn --frozen-lockfile - name: Install Dependencies - - run: yarn electron x64 - name: Download Electron - - run: yarn gulp hygiene - name: Run Hygiene Checks - - run: yarn monaco-compile-check - name: Run Monaco Editor Checks - - run: yarn valid-layers-check - name: Run Valid Layers Checks - - run: yarn compile - name: Compile Sources - - run: yarn download-builtin-extensions - name: Download Built-in Extensions - - run: ./scripts/test.sh --tfs "Unit Tests" - name: Run Unit Tests (Electron) - - run: yarn test-browser --browser chromium --browser webkit - name: Run Unit Tests (Browser) - - run: ./scripts/test-integration.sh --tfs "Integration Tests" - name: Run Integration Tests (Electron) + - run: yarn gulp editor-esm-bundle + name: Editor Distro & ESM Bundle diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml new file mode 100644 index 00000000000..473978c41da --- /dev/null +++ b/.github/workflows/commands.yml @@ -0,0 +1,23 @@ +name: Commands +on: + issue_comment: + types: [created] + +# also make changes in ./on-label.yml +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'microsoft/vscode-github-triage-actions' + path: ./actions + ref: v3 + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Run Commands + uses: ./actions/commands + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + config-path: commands diff --git a/.github/workflows/feature-request.yml b/.github/workflows/feature-request.yml new file mode 100644 index 00000000000..8a97702c451 --- /dev/null +++ b/.github/workflows/feature-request.yml @@ -0,0 +1,41 @@ +name: Feature Request Manager +on: + repository_dispatch: + issues: + types: [milestoned] + schedule: + - cron: 20 2 * * * # 4:20am Zurich + +# also make changes in ./on-label.yml +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: 'microsoft/vscode-github-triage-actions' + path: ./actions + ref: v3 + - name: Install Actions + if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request') + run: npm install --production --prefix ./actions + - name: Run Feature Request Manager + if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request') + uses: ./actions/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..a5e65296b64 --- /dev/null +++ b/.github/workflows/locker.yml @@ -0,0 +1,24 @@ +name: Locker +on: + schedule: + - cron: 20 23 * * * # 4:20pm Redmond + repository_dispatch: + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'microsoft/vscode-github-triage-actions' + path: ./actions + ref: v3 + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Run Locker + uses: ./actions/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..2a1ae4d5f1c --- /dev/null +++ b/.github/workflows/needs-more-info-closer.yml @@ -0,0 +1,26 @@ +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: 'microsoft/vscode-github-triage-actions' + path: ./actions + ref: v3 + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Run Needs More Info Closer + uses: ./actions/needs-more-info-closer + with: + label: needs more info + closeDays: 7 + 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!" + pingDays: 300 + pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information." diff --git a/.github/workflows/on-label.yml b/.github/workflows/on-label.yml new file mode 100644 index 00000000000..69bc7ae0377 --- /dev/null +++ b/.github/workflows/on-label.yml @@ -0,0 +1,69 @@ +name: On Label +on: + issues: + types: [labeled] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: v3 + path: ./actions + - name: Install Actions + run: npm install --production --prefix ./actions + + # source of truth in ./author-verified.yml + - name: Checkout Repo + if: contains(github.event.issue.labels.*.name, 'author-verification-requested') + uses: actions/checkout@v2 + with: + path: ./repo + fetch-depth: 0 + - name: Run Author Verified + if: 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 + + # source of truth in ./commands.yml + - name: Run Commands + uses: ./actions/commands + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + config-path: commands + + + # source of truth in ./feature-request.yml + - name: Run Feature Request Manager + if: contains(github.event.issue.labels.*.name, 'feature-request') + uses: ./actions/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 + + # source of truth in ./test-plan-item-validator.yml + - 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: ./actions/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/.github/workflows/on-open.yml b/.github/workflows/on-open.yml new file mode 100644 index 00000000000..400f947e788 --- /dev/null +++ b/.github/workflows/on-open.yml @@ -0,0 +1,45 @@ +name: On Open +on: + issues: + types: [opened] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: v3 + path: ./actions + - name: Install Actions + run: npm install --production --prefix ./actions + + - name: Run CopyCat (JacksonKearl/testissues) + uses: ./actions/copycat + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + owner: JacksonKearl + repo: testissues + - name: Run CopyCat (chrmarti/testissues) + uses: ./actions/copycat + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + owner: chrmarti + repo: testissues + + - name: Run New Release + uses: ./actions/new-release + with: + label: new release + labelColor: "006b75" + labelDescription: Issues found in a recent release of VS Code + days: 5 + + - name: Run Clipboard Labeler + uses: ./actions/regex-labeler + with: + label: "invalid" + mustNotMatch: "^We have written the needed data into your clipboard because it was too large to send\\. Please paste\\.$" + comment: "It looks like you're using the VS Code Issue Reporter but did not paste the text generated into the created issue. We've closed this issue, please open a new one contining the text we placed in your clipboard.\n\nHappy Coding!" diff --git a/.github/workflows/test-plan-item-validator.yml b/.github/workflows/test-plan-item-validator.yml new file mode 100644 index 00000000000..4f24990aaaf --- /dev/null +++ b/.github/workflows/test-plan-item-validator.yml @@ -0,0 +1,27 @@ +name: Test Plan Item Validator +on: + issues: + types: [edited] + +# also edit in ./on-label.yml +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: 'microsoft/vscode-github-triage-actions' + path: ./actions + ref: v3 + - name: Install Actions + if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') + run: npm install --production --prefix ./actions + - 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: ./actions/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/.gitignore b/.gitignore index 160c42ed74b..e73dd4d9e8c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ out-editor/ out-editor-src/ out-editor-build/ out-editor-esm/ +out-editor-esm-bundle/ out-editor-min/ out-monaco-editor-core/ out-vscode/ diff --git a/.vscode/launch.json b/.vscode/launch.json index d0e6ded3f5b..a9929675583 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,6 @@ "request": "attach", "name": "Attach to Extension Host", "port": 5870, - "restart": true, "outFiles": [ "${workspaceFolder}/out/**/*.js" ] @@ -184,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/.vscode/tasks.json b/.vscode/tasks.json index aa94be61126..30b783943e0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -31,20 +31,6 @@ } } }, - { - "type": "npm", - "script": "strict-function-types-watch", - "label": "TS - Strict Function Types", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "problemMatcher": { - "base": "$tsc-watch", - "owner": "typescript-function-types", - "applyTo": "allDocuments" - } - }, { "label": "Run tests", "type": "shell", diff --git a/.yarnrc b/.yarnrc index 7808166004a..f1127a47b09 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "7.1.11" +target "7.2.1" runtime "electron" 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/README.md b/README.md index 0de5f2f71da..1d3bc28f9bb 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ please see the document [How to Contribute](https://github.com/Microsoft/vscode/ * Ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/vscode) * [Request a new feature](CONTRIBUTING.md) -* Up vote [popular feature requests](https://github.com/Microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) +* Upvote [popular feature requests](https://github.com/Microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) * [File an issue](https://github.com/Microsoft/vscode/issues) * Follow [@code](https://twitter.com/code) and let us know what you think! diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 7bd723dcbff..18299cdf169 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -23,7 +23,7 @@ This project incorporates components from the projects listed below. The origina 16. fadeevab/make.tmbundle (https://github.com/fadeevab/make.tmbundle) 17. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift) 18. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/) -19. Ikuyadeu/vscode-R version 0.5.5 (https://github.com/Ikuyadeu/vscode-R) +19. Ikuyadeu/vscode-R version 1.1.8 (https://github.com/Ikuyadeu/vscode-R) 20. insane version 2.6.2 (https://github.com/bevacqua/insane) 21. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) 22. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) @@ -33,7 +33,7 @@ This project incorporates components from the projects listed below. The origina 26. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) 27. language-docker (https://github.com/moby/moby) 28. language-less version 0.34.2 (https://github.com/atom/language-less) -29. language-php version 0.44.3 (https://github.com/atom/language-php) +29. language-php version 0.44.4 (https://github.com/atom/language-php) 30. language-rust version 0.4.12 (https://github.com/zargony/atom-language-rust) 31. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) 32. marked version 0.6.2 (https://github.com/markedjs/marked) @@ -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.5.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 @@ -2807,4 +2779,4 @@ Apache License See the License for the specific language governing permissions and limitations under the License. ========================================= -END OF Web Background Synchronization NOTICES AND INFORMATION +END OF Web Background Synchronization NOTICES AND INFORMATION \ No newline at end of file diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 6be05972f7f..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 @@ -207,7 +241,7 @@ steps: "toolVersion": "1.0" } ] - SessionTimeout: 120 + SessionTimeout: 60 displayName: Notarization - script: | 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 db6524be03b..6c28724824d 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -77,6 +77,23 @@ steps: yarn postinstall displayName: Run postinstall scripts condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), 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) # 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 deleted file mode 100644 index c2755a8478b..00000000000 --- a/build/builtInExtensions.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "name": "ms-vscode.node-debug", - "version": "1.43.0", - "repo": "https://github.com/Microsoft/vscode-node-debug", - "metadata": { - "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - }, - { - "name": "ms-vscode.node-debug2", - "version": "1.42.1", - "repo": "https://github.com/Microsoft/vscode-node-debug2", - "metadata": { - "id": "36d19e17-7569-4841-a001-947eb18602b2", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - }, - { - "name": "ms-vscode.references-view", - "version": "0.0.47", - "repo": "https://github.com/Microsoft/vscode-reference-view", - "metadata": { - "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", - "publisherId": { - "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", - "publisherName": "ms-vscode", - "displayName": "Microsoft", - "flags": "verified" - }, - "publisherDisplayName": "Microsoft" - } - }, - { - "name": "ms-vscode.js-debug-nightly", - "version": "2020.2.1117", - "forQualities": [ - "insider" - ], - "repo": "https://github.com/Microsoft/vscode-js-debug", - "metadata": { - "id": "7acbb4ce-c85a-49d4-8d95-a8054406ae97", - "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..e5956179567 100644 --- a/build/builtin/browser-main.js +++ b/build/builtin/browser-main.js @@ -6,12 +6,10 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); -// @ts-ignore review const { remote } = require('electron'); const dialog = remote.dialog; -const productJsonPath = path.join(__dirname, '..', '..', 'product.json'); -const builtInExtensionsPath = path.join(__dirname, '..', 'builtInExtensions.json'); +const builtInExtensionsPath = path.join(__dirname, '..', '..', 'product.json'); const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); function readJson(filePath) { @@ -52,7 +50,6 @@ function render(el, state) { } const ul = document.createElement('ul'); - const { quality } = readJson(productJsonPath); const { builtin, control } = state; for (const ext of builtin) { @@ -63,10 +60,6 @@ function render(el, state) { const name = document.createElement('code'); name.textContent = ext.name; - if (quality && ext.forQualities && !ext.forQualities.includes(quality)) { - name.textContent += ` (only on ${ext.forQualities.join(', ')})`; - } - li.appendChild(name); const form = document.createElement('form'); @@ -117,7 +110,7 @@ function render(el, state) { function main() { const el = document.getElementById('extensions'); - const builtin = readJson(builtInExtensionsPath); + const builtin = readJson(builtInExtensionsPath).builtInExtensions; let control; try { diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 105402470d8..21ba45a7360 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -16,6 +16,8 @@ const cp = require('child_process'); const compilation = require('./lib/compilation'); const monacoapi = require('./monaco/api'); const fs = require('fs'); +const webpack = require('webpack'); +const webpackGulp = require('webpack-stream'); let root = path.dirname(__dirname); let sha1 = util.getVersion(root); @@ -70,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: [] }); @@ -129,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') { @@ -147,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)); @@ -332,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( @@ -358,6 +367,49 @@ gulp.task('editor-distro', ) ); +const bundleEditorESMTask = task.define('editor-esm-bundle-webpack', () => { + const result = es.through(); + + const webpackConfigPath = path.join(root, 'build/monaco/monaco.webpack.config.js'); + + const webpackConfig = { + ...require(webpackConfigPath), + ...{ mode: 'production' } + }; + + const webpackDone = (err, stats) => { + if (err) { + result.emit('error', err); + return; + } + const { compilation } = stats; + if (compilation.errors.length > 0) { + result.emit('error', compilation.errors.join('\n')); + } + if (compilation.warnings.length > 0) { + result.emit('data', compilation.warnings.join('\n')); + } + }; + + return webpackGulp(webpackConfig, webpack, webpackDone) + .pipe(gulp.dest('out-editor-esm-bundle')); +}); + +gulp.task('editor-esm-bundle', + task.series( + task.parallel( + util.rimraf('out-editor-src'), + util.rimraf('out-editor-esm'), + util.rimraf('out-monaco-editor-core'), + util.rimraf('out-editor-esm-bundle'), + ), + extractEditorSrcTask, + createESMSourcesAndResourcesTask, + compileEditorESMTask, + bundleEditorESMTask, + ) +); + gulp.task('monacodts', task.define('monacodts', () => { const result = monacoapi.execute(); fs.writeFileSync(result.filePath, result.content); @@ -403,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/builtInExtensions.js b/build/lib/builtInExtensions.js index 3f8dfd11132..ebcf8bc8ddb 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -18,7 +18,7 @@ const fancyLog = require('fancy-log'); const ansiColors = require('ansi-colors'); const root = path.dirname(path.dirname(__dirname)); -const builtInExtensions = require('../builtInExtensions.json'); +const builtInExtensions = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')).builtInExtensions; const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); const ENABLE_LOGGING = !process.env['VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE']; 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..1f073be2dc8 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"); @@ -27,7 +28,6 @@ const util = require('./util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; -const product = require('../../product.json'); function fromLocal(extensionPath) { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); const input = fs.existsSync(webpackFilename) @@ -181,14 +181,13 @@ 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', 'ms-vscode.node-debug2', - 'ms.vscode.js-debug-nightly' ]; -const builtInExtensions = require('../builtInExtensions.json') - .filter(({ forQualities }) => { var _a; return !product.quality || ((_a = forQualities === null || forQualities === void 0 ? void 0 : forQualities.includes) === null || _a === void 0 ? void 0 : _a.call(forQualities, product.quality)) !== false; }); +const builtInExtensions = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')).builtInExtensions; function packageLocalExtensionsStream() { const localExtensionDescriptions = glob.sync('extensions/*/package.json') .map(manifestPath => { diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 9c90cb60c47..715daac0a0f 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -27,7 +27,6 @@ const util = require('./util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; -const product = require('../../product.json'); function fromLocal(extensionPath: string): Stream { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); @@ -216,23 +215,21 @@ 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', 'ms-vscode.node-debug2', - 'ms.vscode.js-debug-nightly' ]; interface IBuiltInExtension { name: string; version: string; repo: string; - forQualities?: ReadonlyArray; metadata: any; } -const builtInExtensions = (require('../builtInExtensions.json')) - .filter(({ forQualities }) => !product.quality || forQualities?.includes?.(product.quality) !== false); +const builtInExtensions: IBuiltInExtension[] = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')).builtInExtensions; export function packageLocalExtensionsStream(): NodeJS.ReadWriteStream { const localExtensionDescriptions = (glob.sync('extensions/*/package.json')) 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 fe693b695b4..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" @@ -302,6 +306,10 @@ "name": "vs/workbench/services/textMate", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/workingCopy", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/workspaces", "project": "vscode-workbench" @@ -333,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 0fbaf7335b7..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"); @@ -135,11 +136,12 @@ function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, } else { const memberName = member.name.text; + const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`); if (isStatic(member)) { - usage.push(`a = ${staticTypeName}.${memberName};`); + usage.push(`a = ${staticTypeName}${memberAccess};`); } else { - usage.push(`a = (<${instanceTypeName}>b).${memberName};`); + usage.push(`a = (<${instanceTypeName}>b)${memberAccess};`); } } } diff --git a/build/monaco/api.ts b/build/monaco/api.ts index 511768ee64b..f3542988a4b 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -167,10 +167,11 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati result = result.replace(memberText, ''); } else { const memberName = (member.name).text; + const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`); if (isStatic(member)) { - usage.push(`a = ${staticTypeName}.${memberName};`); + usage.push(`a = ${staticTypeName}${memberAccess};`); } else { - usage.push(`a = (<${instanceTypeName}>b).${memberName};`); + usage.push(`a = (<${instanceTypeName}>b)${memberAccess};`); } } } catch (err) { diff --git a/src/vs/base/parts/tree/browser/treeUtils.ts b/build/monaco/esm.core.js similarity index 51% rename from src/vs/base/parts/tree/browser/treeUtils.ts rename to build/monaco/esm.core.js index 6d536a7fa5a..b84b5fb4f41 100644 --- a/src/vs/base/parts/tree/browser/treeUtils.ts +++ b/build/monaco/esm.core.js @@ -3,16 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as _ from 'vs/base/parts/tree/browser/tree'; +// Entry file for webpack bunlding. -export function isEqualOrParent(tree: _.ITree, element: any, candidateParent: any): boolean { - const nav = tree.getNavigator(element); +import * as monaco from 'monaco-editor-core'; - do { - if (element === candidateParent) { - return true; - } - } while (element = nav.parent()); +self.MonacoEnvironment = { + getWorkerUrl: function (moduleId, label) { + return './editor.worker.bundle.js'; + } +}; - return false; -} +monaco.editor.create(document.getElementById('container'), { + value: [ + 'var hello = "hello world";' + ].join('\n'), + language: 'javascript' +}); diff --git a/build/monaco/monaco.webpack.config.js b/build/monaco/monaco.webpack.config.js new file mode 100644 index 00000000000..974a341a197 --- /dev/null +++ b/build/monaco/monaco.webpack.config.js @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const path = require('path'); + +module.exports = { + mode: 'production', + entry: { + 'core': './build/monaco/esm.core.js', + 'editor.worker': './out-monaco-editor-core/esm/vs/editor/editor.worker.js' + }, + output: { + globalObject: 'self', + filename: '[name].bundle.js', + path: path.resolve(__dirname, 'dist') + }, + module: { + rules: [{ + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, { + test: /\.ttf$/, + use: ['file-loader'] + }] + }, + resolve: { + alias: { + 'monaco-editor-core': path.resolve(__dirname, '../../out-monaco-editor-core/esm/vs/editor/editor.main.js'), + } + }, + stats: { + all: false, + modules: true, + maxModules: 0, + errors: true, + warnings: true, + // our additional options + moduleTrace: true, + errorDetails: true, + chunks: true + } +}; diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 5a937a80bd8..7a2320d8289 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -13,7 +13,7 @@ const yarn = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'; * @param {*} [opts] */ function yarnInstall(location, opts) { - opts = opts || {}; + opts = opts || { env: process.env }; opts.cwd = location; opts.stdio = 'inherit'; @@ -52,8 +52,6 @@ extensions.forEach(extension => yarnInstall(`extensions/${extension}`)); function yarnInstallBuildDependencies() { // make sure we install the deps of build/lib/watch for the system installed // node, since that is the driver of gulp - //@ts-ignore - const env = Object.assign({}, process.env); const watchPath = path.join(path.dirname(__dirname), 'lib', 'watch'); const yarnrcPath = path.join(watchPath, '.yarnrc'); @@ -66,7 +64,7 @@ target "${target}" runtime "${runtime}"`; fs.writeFileSync(yarnrcPath, yarnrc, 'utf8'); - yarnInstall(watchPath, { env }); + yarnInstall(watchPath); } yarnInstall(`build`); // node modules required for build 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 6f9ebb5b34b..1817f2c9345 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.3", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "^3.8.1-rc", + "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 ee1e7bfb294..e43e8eb3739 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.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.3.tgz#3db5c0765545ab8637be71f333a104a965a9ca3f" + integrity sha512-+bMdgqjMN/Z77a6NlY/I3U5LlRDbnmaAk6lDveAPKwSpcPM4tKAuYsvYF8xjhOPXhOYGe/73vVLVez5PW+jqhw== minimist@~0.0.1: version "0.0.10" @@ -2458,10 +2458,10 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^3.8.1-rc: - version "3.8.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.1-rc.tgz#f94333c14da70927ccd887be2e91be652a9a09f6" - integrity sha512-aOIe066DyZn2uYIiND6fXMUUJ70nxwu/lKhA92QuQzXyC86fr0ywo1qvO8l2m0EnDcfjprYPuFRgNgDj7U2GlQ== +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" diff --git a/cglicenses.json b/cglicenses.json index 1e9287cbaf2..0d588440ae2 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -81,5 +81,93 @@ "prependLicenseText": [ "Copyright (c) Microsoft Corporation. All rights reserved." ] + }, + { + // Reason: The license at https://github.com/rbuckton/reflect-metadata/blob/master/LICENSE + // does not include a clear Copyright statement (it's in https://github.com/rbuckton/reflect-metadata/blob/master/CopyrightNotice.txt). + "name": "reflect-metadata", + "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/cgmanifest.json b/cgmanifest.json index 40d6e42aa78..6ee98779585 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "d17dfabfcba7bd0bc994b8dac5f5d2000bef572c" + "commitHash": "456c9f09a28ef59c82e43fe0602eddddeb211c77" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "7.1.11" + "version": "7.2.1" }, { "component": { diff --git a/extensions/bat/package.json b/extensions/bat/package.json index 00bd84e4ae4..dc543aef402 100644 --- a/extensions/bat/package.json +++ b/extensions/bat/package.json @@ -23,7 +23,7 @@ }], "snippets": [{ "language": "bat", - "path": "./snippets/batchfile.snippets.json" + "path": "./snippets/batchfile.code-snippets" }] } -} \ No newline at end of file +} diff --git a/extensions/bat/snippets/batchfile.snippets.json b/extensions/bat/snippets/batchfile.code-snippets similarity index 100% rename from extensions/bat/snippets/batchfile.snippets.json rename to extensions/bat/snippets/batchfile.code-snippets diff --git a/extensions/coffeescript/package.json b/extensions/coffeescript/package.json index df5f8ff7c1f..9bc96cca5d8 100644 --- a/extensions/coffeescript/package.json +++ b/extensions/coffeescript/package.json @@ -28,7 +28,7 @@ ], "snippets": [{ "language": "coffeescript", - "path": "./snippets/coffeescript.snippets.json" + "path": "./snippets/coffeescript.code-snippets" }] } -} \ No newline at end of file +} diff --git a/extensions/coffeescript/snippets/coffeescript.snippets.json b/extensions/coffeescript/snippets/coffeescript.code-snippets similarity index 100% rename from extensions/coffeescript/snippets/coffeescript.snippets.json rename to extensions/coffeescript/snippets/coffeescript.code-snippets diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index be2d2e8ebae..f929c260fd5 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -18,8 +18,8 @@ "watch": "gulp watch-extension:configuration-editing" }, "dependencies": { - "jsonc-parser": "^2.1.1", - "vscode-nls": "^4.0.0" + "jsonc-parser": "^2.2.1", + "vscode-nls": "^4.1.1" }, "contributes": { "languages": [ @@ -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/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 0d47b27bec9..9cb171915fc 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -48,6 +48,16 @@ "type": "string", "description": "The user VS Code Server will be started with. The default is the same user as the container." }, + "initializeCommand": { + "type": [ + "string", + "array" + ], + "description": "A command to run locally before anything else. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, "postCreateCommand": { "type": [ "string", @@ -132,21 +142,89 @@ } } }, - "dockerFileContainer": { + "dockerfileContainer": { + "oneOf": [ + { + "type": "object", + "properties": { + "build": { + "type": "object", + "description": "Docker build-related options.", + "allOf": [ + { + "type": "object", + "properties": { + "dockerfile": { + "type": "string", + "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." + }, + "context": { + "type": "string", + "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." + } + }, + "required": [ + "dockerfile" + ] + }, + { + "$ref": "#/definitions/buildOptions" + } + ] + } + }, + "required": [ + "build" + ] + }, + { + "allOf": [ + { + "type": "object", + "properties": { + "dockerFile": { + "type": "string", + "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." + }, + "context": { + "type": "string", + "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." + } + }, + "required": [ + "dockerFile" + ] + }, + { + "type": "object", + "properties": { + "build": { + "description": "Docker build-related options.", + "$ref": "#/definitions/buildOptions" + } + } + } + ] + } + ] + }, + "buildOptions": { "type": "object", "properties": { - "dockerFile": { + "target": { "type": "string", - "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." + "description": "Target stage in a multi-stage build." }, - "context": { - "type": "string", - "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." + "args": { + "type": "object", + "additionalProperties": { + "type": [ + "string" + ] + }, + "description": "Build arguments." } - }, - "required": [ - "dockerFile" - ] + } }, "imageContainer": { "type": "object", @@ -212,7 +290,7 @@ { "oneOf": [ { - "$ref": "#/definitions/dockerFileContainer" + "$ref": "#/definitions/dockerfileContainer" }, { "$ref": "#/definitions/imageContainer" diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 644e9bb1192..966073e23f8 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -7,6 +7,7 @@ import { getLocation, parse, visit } from 'jsonc-parser'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { SettingsDocument } from './settingsDocumentHelper'; +import { provideInstalledExtensionProposals } from './extensionsProposals'; const localize = nls.loadMessageBundle(); export function activate(context: vscode.ExtensionContext): void { @@ -80,7 +81,7 @@ function registerExtensionsCompletionsInExtensionsDocument(): vscode.Disposable const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); if (location.path[0] === 'recommendations') { const extensionsContent = parse(document.getText()); - return provideInstalledExtensionProposals(extensionsContent, range); + return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], range, false); } return []; } @@ -94,41 +95,13 @@ function registerExtensionsCompletionsInWorkspaceConfigurationDocument(): vscode const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') { const extensionsContent = parse(document.getText())['extensions']; - return provideInstalledExtensionProposals(extensionsContent, range); + return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], range, false); } return []; } }); } -function provideInstalledExtensionProposals(extensionsContent: IExtensionsContent, range: vscode.Range): vscode.ProviderResult { - const alreadyEnteredExtensions = extensionsContent && extensionsContent.recommendations || []; - if (Array.isArray(alreadyEnteredExtensions)) { - const knownExtensionProposals = vscode.extensions.all.filter(e => - !(e.id.startsWith('vscode.') - || e.id === 'Microsoft.vscode-markdown' - || alreadyEnteredExtensions.indexOf(e.id) > -1)); - if (knownExtensionProposals.length) { - return knownExtensionProposals.map(e => { - const item = new vscode.CompletionItem(e.id); - const insertText = `"${e.id}"`; - item.kind = vscode.CompletionItemKind.Value; - item.insertText = insertText; - item.range = range; - item.filterText = insertText; - return item; - }); - } else { - const example = new vscode.CompletionItem(localize('exampleExtension', "Example")); - example.insertText = '"vscode.csharp"'; - example.kind = vscode.CompletionItemKind.Value; - example.range = range; - return [example]; - } - } - return undefined; -} - vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, { provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult { const result: vscode.SymbolInformation[] = []; diff --git a/extensions/configuration-editing/src/extensionsProposals.ts b/extensions/configuration-editing/src/extensionsProposals.ts new file mode 100644 index 00000000000..60533ae2975 --- /dev/null +++ b/extensions/configuration-editing/src/extensionsProposals.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + + +export function provideInstalledExtensionProposals(existing: string[], range: vscode.Range, includeBuiltinExtensions: boolean): vscode.ProviderResult { + if (Array.isArray(existing)) { + const extensions = includeBuiltinExtensions ? vscode.extensions.all : vscode.extensions.all.filter(e => !(e.id.startsWith('vscode.') || e.id === 'Microsoft.vscode-markdown')); + const knownExtensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1); + if (knownExtensionProposals.length) { + return knownExtensionProposals.map(e => { + const item = new vscode.CompletionItem(e.id); + const insertText = `"${e.id}"`; + item.kind = vscode.CompletionItemKind.Value; + item.insertText = insertText; + item.range = range; + item.filterText = insertText; + return item; + }); + } else { + const example = new vscode.CompletionItem(localize('exampleExtension', "Example")); + example.insertText = '"vscode.csharp"'; + example.kind = vscode.CompletionItemKind.Value; + example.range = range; + return [example]; + } + } + return undefined; +} + diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index 3fd25056379..4a7f80c2a2a 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { getLocation, Location } from 'jsonc-parser'; +import { getLocation, Location, parse } from 'jsonc-parser'; import * as nls from 'vscode-nls'; +import { provideInstalledExtensionProposals } from './extensionsProposals'; const localize = nls.loadMessageBundle(); @@ -13,7 +14,7 @@ export class SettingsDocument { constructor(private document: vscode.TextDocument) { } - public provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): vscode.ProviderResult { + public provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): vscode.ProviderResult { const location = getLocation(this.document.getText(), this.document.offsetAt(position)); const range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position); @@ -41,6 +42,15 @@ export class SettingsDocument { }); } + // sync.ignoredExtensions + if (location.path[0] === 'sync.ignoredExtensions') { + let ignoredExtensions = []; + try { + ignoredExtensions = parse(this.document.getText())['sync.ignoredExtensions']; + } catch (e) {/* ignore error */ } + return provideInstalledExtensionProposals(ignoredExtensions, range, true); + } + return this.provideLanguageOverridesCompletionItems(location, position); } diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index d5aafed1189..36aab5fd224 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -7,12 +7,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== -jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" + integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== 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/package.json b/extensions/cpp/package.json index c3ed8538bd8..e02009fb57f 100644 --- a/extensions/cpp/package.json +++ b/extensions/cpp/package.json @@ -75,11 +75,11 @@ "snippets": [ { "language": "c", - "path": "./snippets/c.json" + "path": "./snippets/c.code-snippets" }, { "language": "cpp", - "path": "./snippets/cpp.json" + "path": "./snippets/cpp.code-snippets" } ] } diff --git a/extensions/cpp/snippets/c.json b/extensions/cpp/snippets/c.code-snippets similarity index 100% rename from extensions/cpp/snippets/c.json rename to extensions/cpp/snippets/c.code-snippets diff --git a/extensions/cpp/snippets/cpp.json b/extensions/cpp/snippets/cpp.code-snippets similarity index 100% rename from extensions/cpp/snippets/cpp.json rename to extensions/cpp/snippets/cpp.code-snippets 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/csharp/package.json b/extensions/csharp/package.json index 3841eabd132..74bbb283788 100644 --- a/extensions/csharp/package.json +++ b/extensions/csharp/package.json @@ -37,7 +37,7 @@ ], "snippets": [{ "language": "csharp", - "path": "./snippets/csharp.json" + "path": "./snippets/csharp.code-snippets" }] } -} \ No newline at end of file +} diff --git a/extensions/csharp/snippets/csharp.json b/extensions/csharp/snippets/csharp.code-snippets similarity index 100% rename from extensions/csharp/snippets/csharp.json rename to extensions/csharp/snippets/csharp.code-snippets diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index d10cbc6e10c..4d3cffac258 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -806,7 +806,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^6.1.0", + "vscode-languageclient": "^6.1.1", "vscode-nls": "^4.1.1" }, "devDependencies": { diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index b13b8f650e1..593ecdff80a 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -9,11 +9,11 @@ }, "main": "./out/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.3-next.27", - "vscode-languageserver": "^6.1.0" + "vscode-css-languageservice": "^4.1.1", + "vscode-languageserver": "^6.1.1" }, "devDependencies": { - "@types/mocha": "2.2.33", + "@types/mocha": "7.0.1", "@types/node": "^12.11.7", "glob": "^7.1.6", "mocha": "^7.0.1", diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index cb93fa97899..902123900d0 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/mocha@2.2.33": - version "2.2.33" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.33.tgz#d79a0061ec270379f4d9e225f4096fb436669def" - integrity sha1-15oAYewnA3n02eIl9AlvtDZmne8= +"@types/mocha@7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e" + integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q== "@types/node@^12.11.7": version "12.11.7" @@ -689,12 +689,12 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-css-languageservice@^4.0.3-next.27: - version "4.0.3-next.27" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.27.tgz#04f52bfdd85ac7eed8d82a0133aaabb4c0e18f7f" - integrity sha512-MU8sHQABb1WnzOwgazIdO4lXG7cGzFKd7VKi5j67uWTNsqSrbAVSoKGjSyOLq/o6h1L5DGG1Og/7q403z6D04g== +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-next.1" + vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.15.1" vscode-nls "^4.1.1" vscode-uri "^2.1.1" @@ -704,30 +704,30 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageserver-protocol@^3.15.2: - version "3.15.2" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.2.tgz#e52c62923140b2655ad2472f6f29cfb83bacf5b8" - integrity sha512-GdL05JKOgZ76RDg3suiGCl9enESM7iQgGw4x93ibTh4sldvZmakHmTeZ4iUApPPGKf6O3OVBtrsksBXnHYaxNg== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" vscode-languageserver-types "3.15.1" -vscode-languageserver-textdocument@^1.0.1-next.1: - version "1.0.1-next.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1-next.1.tgz#c8f2f792c7c88d33ea8441ca04bfb8376896b671" - integrity sha512-Cmt0KsNxouns+d7/Kw/jWtWU9Z3h56z1qAA8utjDOEqrDcrTs2rDXv3EJRa99nuKM3wVf6DbWym1VqL9q71XPA== +vscode-languageserver-textdocument@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f" + integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA== vscode-languageserver-types@3.15.1, vscode-languageserver-types@^3.15.1: version "3.15.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== -vscode-languageserver@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.0.tgz#f0ff149b06d1961f49ed03385ecc2a96bcaddcde" - integrity sha512-Q5kUJegYclTZMnKUaEcxJK41Ozp6qJhhoFJYj0w8y8j9JXdKT479LE945QCKRvSgWfsqTSUmgsozVTUIwQQxHw== +vscode-languageserver@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz#d76afc68172c27d4327ee74332b468fbc740d762" + integrity sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ== dependencies: - vscode-languageserver-protocol "^3.15.2" + vscode-languageserver-protocol "^3.15.3" vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/css-language-features/yarn.lock b/extensions/css-language-features/yarn.lock index b90ab508aaa..4494f0038a2 100644 --- a/extensions/css-language-features/yarn.lock +++ b/extensions/css-language-features/yarn.lock @@ -640,18 +640,18 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageclient@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.0.tgz#ee67c0b7818c42ce0281572d05c89adfcc4f5a38" - integrity sha512-Tcp0VoOaa0YzxL4nEfK9tsmcy76Eo8jNLvFQZwh2c8oMm02luL8uGYPLQNAiZ3XGgegfcwiQFZMqbW7DNV0vxA== +vscode-languageclient@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.1.tgz#91b62e416c5abbf2013ae3726f314a19c22a8457" + integrity sha512-mB6d8Tg+82l8EFUfR+SBu0+lCshyKVgC5E5+MQ0/BJa+9AgeBjtG5npoGaCo4/VvWzK0ZRGm85zU5iRp1RYPIA== dependencies: semver "^6.3.0" - vscode-languageserver-protocol "^3.15.2" + vscode-languageserver-protocol "^3.15.3" -vscode-languageserver-protocol@^3.15.2: - version "3.15.2" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.2.tgz#e52c62923140b2655ad2472f6f29cfb83bacf5b8" - integrity sha512-GdL05JKOgZ76RDg3suiGCl9enESM7iQgGw4x93ibTh4sldvZmakHmTeZ4iUApPPGKf6O3OVBtrsksBXnHYaxNg== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.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 54edf5ccbdc..75d77925b0d 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -19,10 +19,10 @@ "watch": "gulp watch-extension:extension-editing" }, "dependencies": { - "jsonc-parser": "^2.1.1", + "jsonc-parser": "^2.2.1", "markdown-it": "^8.3.1", "parse5": "^3.0.2", - "vscode-nls": "^4.0.0" + "vscode-nls": "^4.1.1" }, "contributes": { "jsonValidation": [ @@ -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/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index d2f69a169d8..50adf31c099 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -29,10 +29,10 @@ entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= -jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== linkify-it@^2.0.0: version "2.0.3" @@ -74,7 +74,7 @@ uc.micro@^1.0.1, uc.micro@^1.0.3: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" integrity sha1-ftUNXg+an7ClczeSWfKndFjVAZI= -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" + integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index 46584d1b7ef..d1f7e2324e7 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": "af037b23ca4c61b02799957a61cbd05b44355caf" } }, "license": "MIT", diff --git a/extensions/fsharp/package.json b/extensions/fsharp/package.json index b7050e22b4e..4341fdc30c2 100644 --- a/extensions/fsharp/package.json +++ b/extensions/fsharp/package.json @@ -23,7 +23,7 @@ }], "snippets": [{ "language": "fsharp", - "path": "./snippets/fsharp.json" + "path": "./snippets/fsharp.code-snippets" }] } } diff --git a/extensions/fsharp/snippets/fsharp.json b/extensions/fsharp/snippets/fsharp.code-snippets similarity index 100% rename from extensions/fsharp/snippets/fsharp.json rename to extensions/fsharp/snippets/fsharp.code-snippets diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index ed1e9fcb5f8..419feb7f7bb 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/af037b23ca4c61b02799957a61cbd05b44355caf", "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" } ] @@ -905,7 +910,7 @@ "patterns": [ { "name": "binding.fsharp", - "begin": "\\b(let mutable|static let mutable|let inline|let|member val|static member inline|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", + "begin": "\\b(let mutable|static let mutable|static let|let inline|let|member val|static member inline|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", "end": "\\s*(with\\b|=|\\n+=|(?<=\\=))", "beginCaptures": { "1": { @@ -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" @@ -1481,7 +1498,7 @@ "patterns": [ { "name": "variable.other.binding.fsharp", - "match": "(``)(.*)(``)", + "match": "(``)([^`]*)(``)", "captures": { "1": { "name": "string.quoted.single.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 c1cd8b5135c..289aba571e9 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", @@ -447,61 +393,101 @@ "command": "git.stashDrop", "title": "%command.stashDrop%", "category": "Git" + }, + { + "command": "git.timeline.openDiff", + "title": "%command.timelineOpenDiff%", + "icon": "$(compare-changes)", + "category": "Git" + }, + { + "command": "git.timeline.copyCommitId", + "title": "%command.timelineCopyCommitId%", + "category": "Git" + }, + { + "command": "git.timeline.copyCommitMessage", + "title": "%command.timelineCopyCommitMessage%", + "category": "Git" + } + ], + "keybindings": [ + { + "command": "git.stageSelectedRanges", + "key": "ctrl+k ctrl+alt+s", + "mac": "cmd+k cmd+alt+s", + "when": "isInDiffEditor" + }, + { + "command": "git.unstageSelectedRanges", + "key": "ctrl+k ctrl+u", + "mac": "cmd+k cmd+u", + "when": "isInDiffEditor" + }, + { + "command": "git.revertSelectedRanges", + "key": "ctrl+k ctrl+r", + "mac": "cmd+k cmd+r", + "when": "isInDiffEditor" } ], "menus": { "commandPalette": [ + { + "command": "git.setLogLevel", + "when": "config.git.enabled && !git.missing" + }, { "command": "git.clone", - "when": "config.git.enabled" + "when": "config.git.enabled && !git.missing" }, { "command": "git.init", - "when": "config.git.enabled" + "when": "config.git.enabled && !git.missing" }, { "command": "git.openRepository", - "when": "config.git.enabled" + "when": "config.git.enabled && !git.missing" }, { "command": "git.close", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.refresh", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.openFile", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.openHEADFile", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.openChange", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stage", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stageAll", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stageAllTracked", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stageAllUntracked", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stageSelectedRanges", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stageChange", @@ -509,7 +495,7 @@ }, { "command": "git.revertSelectedRanges", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.revertChange", @@ -521,51 +507,63 @@ }, { "command": "git.unstage", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.unstageAll", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.unstageSelectedRanges", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.clean", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.cleanAll", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, + { + "command": "git.cleanAllTracked", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, + { + "command": "git.cleanAllUntracked", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.commit", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.commitStaged", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, + { + "command": "git.commitEmpty", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.commitStagedSigned", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.commitStagedAmend", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.commitAll", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.commitAllSigned", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.commitAllAmend", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.restoreCommitTemplate", @@ -577,111 +575,111 @@ }, { "command": "git.undoCommit", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.checkout", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.branch", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.branchFrom", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.deleteBranch", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.renameBranch", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.pull", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.pullFrom", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.pullRebase", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.pullFrom", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.merge", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.createTag", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.deleteTag", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.fetch", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.fetchPrune", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.fetchAll", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.push", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.pushForce", - "when": "config.git.enabled && config.git.allowForcePush && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && config.git.allowForcePush && gitOpenRepositoryCount != 0" }, { "command": "git.pushTo", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.pushToForce", - "when": "config.git.enabled && config.git.allowForcePush && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && config.git.allowForcePush && gitOpenRepositoryCount != 0" }, { "command": "git.pushWithTags", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.pushWithTagsForce", - "when": "config.git.enabled && config.git.allowForcePush && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && config.git.allowForcePush && gitOpenRepositoryCount != 0" }, { "command": "git.addRemote", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.removeRemote", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.sync", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.syncRebase", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.publish", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.showOutput", @@ -689,43 +687,50 @@ }, { "command": "git.ignore", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stashIncludeUntracked", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stash", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stashPop", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stashPopLatest", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stashApply", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stashApplyLatest", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.stashDrop", - "when": "config.git.enabled && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, + { + "command": "git.timeline.openDiff", + "when": "false" + }, + { + "command": "git.timeline.copyCommitId", + "when": "false" + }, + { + "command": "git.timeline.copyCommitMessage", + "when": "false" } ], "scm/title": [ - { - "command": "git.init", - "group": "navigation", - "when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0" - }, { "command": "git.commit", "group": "navigation", @@ -1016,6 +1021,31 @@ "command": "git.ignore", "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification@3" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "inline" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "inline" + }, + { + "command": "git.ignore", + "when": "scmProvider == git && scmResourceGroup == untracked", + "group": "1_modification@3" } ], "scm/resourceState/context": [ @@ -1199,54 +1229,71 @@ { "command": "git.openFile", "group": "navigation", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.openChange", "group": "navigation", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file" }, { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" } ], "editor/context": [ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "isInDiffRightEditor && config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" } ], "scm/change/title": [ { "command": "git.stageChange", - "when": "originalResourceScheme == git" + "when": "config.git.enabled && !git.missing && originalResourceScheme == git" }, { "command": "git.revertChange", - "when": "originalResourceScheme == git" + "when": "config.git.enabled && !git.missing && originalResourceScheme == git" + } + ], + "timeline/item/context": [ + { + "command": "git.timeline.openDiff", + "group": "1_actions", + "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file\\b/" + }, + { + "command": "git.timeline.copyCommitId", + "group": "5_copy@1", + "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/" + }, + { + "command": "git.timeline.copyCommitMessage", + "group": "5_copy@2", + "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/" } ] }, @@ -1761,11 +1808,46 @@ 72 ] } - } + }, + "viewsWelcome": [ + { + "view": "scm", + "contents": "%view.workbench.scm.disabled%", + "when": "!config.git.enabled" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.missing%", + "when": "config.git.enabled && git.missing" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.empty%", + "when": "config.git.enabled && !git.missing && workbenchState == empty" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.folder%", + "when": "config.git.enabled && !git.missing && workbenchState == folder" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.workspace%", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.emptyWorkspace%", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0" + }, + { + "view": "explorer", + "contents": "%view.workbench.cloneRepository%" + } + ] }, "dependencies": { "byline": "^5.0.0", - "dayjs": "1.8.19", "file-type": "^7.2.0", "iconv-lite": "^0.4.24", "jschardet": "2.1.1", @@ -1778,7 +1860,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/package.nls.json b/extensions/git/package.nls.json index f51154cebf6..e163ffad48d 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -70,6 +70,9 @@ "command.stashApply": "Apply Stash...", "command.stashApplyLatest": "Apply Latest Stash", "command.stashDrop": "Drop Stash...", + "command.timelineOpenDiff": "Open Changes", + "command.timelineCopyCommitId": "Copy Commit ID", + "command.timelineCopyCommitMessage": "Copy Commit Message", "config.enabled": "Whether git is enabled.", "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", @@ -146,5 +149,12 @@ "colors.untracked": "Color for untracked resources.", "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", - "colors.submodule": "Color for submodule resources." + "colors.submodule": "Color for submodule resources.", + "view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", + "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone)" } 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/api/api1.ts b/extensions/git/src/api/api1.ts index 0b6ed8550aa..ad32607f53b 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,8 +5,8 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions } from './git'; -import { Event, SourceControlInputBox, Uri, SourceControl } from 'vscode'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, GitExtension, RefType } from './git'; +import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands } from 'vscode'; import { mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -250,3 +250,76 @@ export class ApiImpl implements API { constructor(private _model: Model) { } } + +function getRefType(type: RefType): string { + switch (type) { + case RefType.Head: return 'Head'; + case RefType.RemoteHead: return 'RemoteHead'; + case RefType.Tag: return 'Tag'; + } + + return 'unknown'; +} + +function getStatus(status: Status): string { + switch (status) { + case Status.INDEX_MODIFIED: return 'INDEX_MODIFIED'; + case Status.INDEX_ADDED: return 'INDEX_ADDED'; + case Status.INDEX_DELETED: return 'INDEX_DELETED'; + case Status.INDEX_RENAMED: return 'INDEX_RENAMED'; + case Status.INDEX_COPIED: return 'INDEX_COPIED'; + case Status.MODIFIED: return 'MODIFIED'; + case Status.DELETED: return 'DELETED'; + case Status.UNTRACKED: return 'UNTRACKED'; + case Status.IGNORED: return 'IGNORED'; + case Status.INTENT_TO_ADD: return 'INTENT_TO_ADD'; + case Status.ADDED_BY_US: return 'ADDED_BY_US'; + case Status.ADDED_BY_THEM: return 'ADDED_BY_THEM'; + case Status.DELETED_BY_US: return 'DELETED_BY_US'; + case Status.DELETED_BY_THEM: return 'DELETED_BY_THEM'; + case Status.BOTH_ADDED: return 'BOTH_ADDED'; + case Status.BOTH_DELETED: return 'BOTH_DELETED'; + case Status.BOTH_MODIFIED: return 'BOTH_MODIFIED'; + } + + return 'UNKNOWN'; +} + +export function registerAPICommands(extension: GitExtension): Disposable { + return Disposable.from( + commands.registerCommand('git.api.getRepositories', () => { + const api = extension.getAPI(1); + return api.repositories.map(r => r.rootUri.toString()); + }), + + commands.registerCommand('git.api.getRepositoryState', (uri: string) => { + const api = extension.getAPI(1); + const repository = api.getRepository(Uri.parse(uri)); + + if (!repository) { + return null; + } + + const state = repository.state; + + const ref = (ref: Ref | undefined) => (ref && { ...ref, type: getRefType(ref.type) }); + const change = (change: Change) => ({ + uri: change.uri.toString(), + originalUri: change.originalUri.toString(), + renameUri: change.renameUri?.toString(), + status: getStatus(change.status) + }); + + return { + HEAD: ref(state.HEAD), + refs: state.refs.map(ref), + remotes: state.remotes, + submodules: state.submodules, + rebaseCommit: state.rebaseCommit, + mergeChanges: state.mergeChanges.map(change), + indexChanges: state.indexChanges.map(change), + workingTreeChanges: state.workingTreeChanges.map(change) + }; + }) + ); +} diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index e1efe1299fc..c688a444427 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -44,6 +44,7 @@ export interface Commit { readonly authorDate?: Date; readonly authorName?: string; readonly authorEmail?: string; + readonly commitDate?: Date; } export interface Submodule { diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index dba21595ea8..e8a696735fc 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,7 +6,7 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions } from './api/git'; @@ -17,6 +17,7 @@ import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineC import { fromGitUri, toGitUri, isGitUri } from './uri'; import { grep, isDescendant, pathEquals } from './util'; import { Log, LogLevel } from './log'; +import { GitTimelineItem } from './timelineProvider'; const localize = nls.loadMessageBundle(); @@ -289,7 +290,7 @@ export class CommandCenter { } @command('git.openResource') - async openResource(resource: Resource): Promise { + async openResource(resource: Resource, preserveFocus: boolean): Promise { const repository = this.model.getRepository(resource.resourceUri); if (!repository) { @@ -300,7 +301,7 @@ export class CommandCenter { const openDiffOnClick = config.get('openDiffOnClick'); if (openDiffOnClick) { - await this._openResource(resource, undefined, true, false); + await this._openResource(resource, undefined, preserveFocus, false); } else { await this.openFile(resource); } @@ -429,25 +430,25 @@ export class CommandCenter { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: case Status.INDEX_ADDED: - return `${basename} (Index)`; + return localize('git.title.index', '{0} (Index)', basename); case Status.MODIFIED: case Status.BOTH_ADDED: case Status.BOTH_MODIFIED: - return `${basename} (Working Tree)`; + return localize('git.title.workingTree', '{0} (Working Tree)', basename); case Status.DELETED_BY_US: - return `${basename} (Theirs)`; + return localize('git.title.theirs', '{0} (Theirs)', basename); case Status.DELETED_BY_THEM: - return `${basename} (Ours)`; + return localize('git.title.ours', '{0} (Ours)', basename); case Status.UNTRACKED: + return localize('git.title.untracked', '{0} (Untracked)', basename); - return `${basename} (Untracked)`; + default: + return ''; } - - return ''; } @command('git.clone') @@ -565,24 +566,29 @@ export class CommandCenter { } @command('git.init') - async init(): Promise { + async init(skipFolderPrompt = false): Promise { let repositoryPath: string | undefined = undefined; let askToOpen = true; if (workspace.workspaceFolders) { - const placeHolder = localize('init', "Pick workspace folder to initialize git repo in"); - const pick = { label: localize('choose', "Choose Folder...") }; - const items: { label: string, folder?: WorkspaceFolder }[] = [ - ...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })), - pick - ]; - const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true }); - - if (!item) { - return; - } else if (item.folder) { - repositoryPath = item.folder.uri.fsPath; + if (skipFolderPrompt && workspace.workspaceFolders.length === 1) { + repositoryPath = workspace.workspaceFolders[0].uri.fsPath; askToOpen = false; + } else { + const placeHolder = localize('init', "Pick workspace folder to initialize git repo in"); + const pick = { label: localize('choose', "Choose Folder...") }; + const items: { label: string, folder?: WorkspaceFolder }[] = [ + ...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })), + pick + ]; + const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true }); + + if (!item) { + return; + } else if (item.folder) { + repositoryPath = item.folder.uri.fsPath; + askToOpen = false; + } } } @@ -1391,12 +1397,16 @@ export class CommandCenter { opts.signoff = true; } + const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges'); + if ( ( // no changes (noStagedChanges && noUnstagedChanges) // or no staged changes and not `all` || (!opts.all && noStagedChanges) + // no staged changes and no tracked unstaged changes + || (noStagedChanges && smartCommitChanges === 'tracked' && repository.workingTreeGroup.resourceStates.every(r => r.type === Status.UNTRACKED)) ) && !opts.empty ) { @@ -1410,7 +1420,7 @@ export class CommandCenter { return false; } - if (opts.all && config.get<'all' | 'tracked'>('smartCommitChanges') === 'tracked') { + if (opts.all && smartCommitChanges === 'tracked') { opts.all = 'tracked'; } @@ -2331,23 +2341,53 @@ export class CommandCenter { return result && result.stash; } - @command('git.openDiff', { repository: false }) - async openDiff(uri: Uri, lhs: string, rhs: string) { + @command('git.timeline.openDiff', { repository: false }) + async timelineOpenDiff(item: TimelineItem, uri: Uri | undefined, _source: string) { + // eslint-disable-next-line eqeqeq + if (uri == null || !GitTimelineItem.is(item)) { + return undefined; + } + const basename = path.basename(uri.fsPath); let title; - if ((lhs === 'HEAD' || lhs === '~') && rhs === '') { - title = `${basename} (Working Tree)`; + if ((item.previousRef === 'HEAD' || item.previousRef === '~') && item.ref === '') { + title = localize('git.title.workingTree', '{0} (Working Tree)', basename); } - else if (lhs === 'HEAD' && rhs === '~') { - title = `${basename} (Index)`; + else if (item.previousRef === 'HEAD' && item.ref === '~') { + title = localize('git.title.index', '{0} (Index)', basename); } else { - title = `${basename} (${lhs.endsWith('^') ? `${lhs.substr(0, 8)}^` : lhs.substr(0, 8)}) \u27f7 ${basename} (${rhs.endsWith('^') ? `${rhs.substr(0, 8)}^` : rhs.substr(0, 8)})`; + title = localize('git.title.diffRefs', '{0} ({1}) ⟷ {0} ({2})', basename, item.shortPreviousRef, item.shortRef); } - return commands.executeCommand('vscode.diff', toGitUri(uri, lhs), rhs === '' ? uri : toGitUri(uri, rhs), 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 }) + async timelineCopyCommitId(item: TimelineItem, _uri: Uri | undefined, _source: string) { + if (!GitTimelineItem.is(item)) { + return; + } + + env.clipboard.writeText(item.ref); + } + + @command('git.timeline.copyCommitMessage', { repository: false }) + async timelineCopyCommitMessage(item: TimelineItem, _uri: Uri | undefined, _source: string) { + if (!GitTimelineItem.is(item)) { + return; + } + + env.clipboard.writeText(item.message); + } + + private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 4453c8a8027..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,13 +46,18 @@ 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. */ export interface LogFileOptions { - /** Max number of log entries to retrieve. If not specified, the default is 32. */ - readonly maxEntries?: number; + /** Optional. The maximum number of log entries to retrieve. */ + readonly maxEntries?: number | string; + /** Optional. The Git sha (hash) to start retrieving log entries from. */ + readonly hash?: string; + /** Optional. Specifies whether to start retrieving log entries in reverse order. */ + readonly reverse?: boolean; + readonly sortByAuthorDate?: boolean; } function parseVersion(raw: string): string { @@ -333,7 +339,7 @@ function sanitizePath(path: string): string { return path.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`); } -const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%P%n%B'; +const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%B'; export class Git { @@ -414,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 { @@ -525,6 +563,7 @@ export interface Commit { authorDate?: Date; authorName?: string; authorEmail?: string; + commitDate?: Date; } export class GitStatusParser { @@ -655,15 +694,16 @@ export function parseGitmodules(raw: string): Submodule[] { return result; } -const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm; +const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm; export function parseGitCommits(data: string): Commit[] { let commits: Commit[] = []; let ref; - let name; - let email; - let date; + let authorName; + let authorEmail; + let authorDate; + let commitDate; let parents; let message; let match; @@ -674,7 +714,7 @@ export function parseGitCommits(data: string): Commit[] { break; } - [, ref, name, email, date, parents, message] = match; + [, ref, authorName, authorEmail, authorDate, commitDate, parents, message] = match; if (message[message.length - 1] === '\n') { message = message.substr(0, message.length - 1); @@ -685,9 +725,10 @@ export function parseGitCommits(data: string): Commit[] { hash: ` ${ref}`.substr(1), message: ` ${message}`.substr(1), parents: parents ? parents.split(' ') : [], - authorDate: new Date(Number(date) * 1000), - authorName: ` ${name}`.substr(1), - authorEmail: ` ${email}`.substr(1) + authorDate: new Date(Number(authorDate) * 1000), + authorName: ` ${authorName}`.substr(1), + authorEmail: ` ${authorEmail}`.substr(1), + commitDate: new Date(Number(commitDate) * 1000), }); } while (true); @@ -814,8 +855,26 @@ export class Repository { } async logFile(uri: Uri, options?: LogFileOptions): Promise { - const maxEntries = options?.maxEntries ?? 32; - const args = ['log', `-n${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z', '--', uri.fsPath]; + const args = ['log', `--format=${COMMIT_FORMAT}`, '-z']; + + if (options?.maxEntries && !options?.reverse) { + args.push(`-n${options.maxEntries}`); + } + + if (options?.hash) { + // If we are reversing, we must add a range (with HEAD) because we are using --ancestry-path for better reverse walking + if (options?.reverse) { + args.push('--reverse', '--ancestry-path', `${options.hash}..HEAD`); + } else { + args.push(options.hash); + } + } + + if (options?.sortByAuthorDate) { + args.push('--author-date-order'); + } + + args.push('--', uri.fsPath); const result = await this.run(args); if (result.exitCode) { diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 7bb5081d12f..2bd8095d418 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -23,6 +23,7 @@ import * as path from 'path'; import * as fs from 'fs'; import { createIPCServer, IIPCServer } from './ipc/ipcServer'; import { GitTimelineProvider } from './timelineProvider'; +import { registerAPICommands } from './api/api1'; const deactivateTasks: { (): Promise; }[] = []; @@ -140,7 +141,7 @@ async function warnAboutMissingGit(): Promise { } } -export async function activate(context: ExtensionContext): Promise { +export async function _activate(context: ExtensionContext): Promise { const disposables: Disposable[] = []; context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose())); @@ -175,12 +176,19 @@ export async function activate(context: ExtensionContext): Promise console.warn(err.message); outputChannel.appendLine(err.message); + commands.executeCommand('setContext', 'git.missing', true); warnAboutMissingGit(); return new GitExtensionImpl(); } } +export async function activate(context: ExtensionContext): Promise { + const result = await _activate(context); + context.subscriptions.push(registerAPICommands(result)); + return result; +} + async function checkGitVersion(info: IGit): Promise { const config = workspace.getConfiguration('git'); const shouldIgnore = config.get('ignoreLegacyWarning') === true; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 4f22b0784bb..4f1d21796ab 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -40,6 +40,29 @@ export const enum ResourceGroupType { export class Resource implements SourceControlResourceState { + static getStatusText(type: Status) { + switch (type) { + case Status.INDEX_MODIFIED: return localize('index modified', "Index Modified"); + case Status.MODIFIED: return localize('modified', "Modified"); + case Status.INDEX_ADDED: return localize('index added', "Index Added"); + case Status.INDEX_DELETED: return localize('index deleted', "Index Deleted"); + case Status.DELETED: return localize('deleted', "Deleted"); + case Status.INDEX_RENAMED: return localize('index renamed', "Index Renamed"); + case Status.INDEX_COPIED: return localize('index copied', "Index Copied"); + case Status.UNTRACKED: return localize('untracked', "Untracked"); + case Status.IGNORED: return localize('ignored', "Ignored"); + case Status.INTENT_TO_ADD: return localize('intent to add', "Intent to Add"); + case Status.BOTH_DELETED: return localize('both deleted', "Both Deleted"); + case Status.ADDED_BY_US: return localize('added by us', "Added By Us"); + case Status.DELETED_BY_THEM: return localize('deleted by them', "Deleted By Them"); + case Status.ADDED_BY_THEM: return localize('added by them', "Added By Them"); + case Status.DELETED_BY_US: return localize('deleted by us', "Deleted By Us"); + case Status.BOTH_ADDED: return localize('both added', "Both Added"); + case Status.BOTH_MODIFIED: return localize('both modified', "Both Modified"); + default: return ''; + } + } + @memoize get resourceUri(): Uri { if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED)) { @@ -110,26 +133,7 @@ export class Resource implements SourceControlResourceState { } private get tooltip(): string { - switch (this.type) { - case Status.INDEX_MODIFIED: return localize('index modified', "Index Modified"); - case Status.MODIFIED: return localize('modified', "Modified"); - case Status.INDEX_ADDED: return localize('index added', "Index Added"); - case Status.INDEX_DELETED: return localize('index deleted', "Index Deleted"); - case Status.DELETED: return localize('deleted', "Deleted"); - case Status.INDEX_RENAMED: return localize('index renamed', "Index Renamed"); - case Status.INDEX_COPIED: return localize('index copied', "Index Copied"); - case Status.UNTRACKED: return localize('untracked', "Untracked"); - case Status.IGNORED: return localize('ignored', "Ignored"); - case Status.INTENT_TO_ADD: return localize('intent to add', "Intent to Add"); - case Status.BOTH_DELETED: return localize('both deleted', "Both Deleted"); - case Status.ADDED_BY_US: return localize('added by us', "Added By Us"); - case Status.DELETED_BY_THEM: return localize('deleted by them', "Deleted By Them"); - case Status.ADDED_BY_THEM: return localize('added by them', "Added By Them"); - case Status.DELETED_BY_US: return localize('deleted by us', "Deleted By Us"); - case Status.BOTH_ADDED: return localize('both added', "Both Added"); - case Status.BOTH_MODIFIED: return localize('both modified', "Both Modified"); - default: return ''; - } + return Resource.getStatusText(this.type); } private get strikeThrough(): boolean { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 12946a2c5fb..8be262d3c25 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -192,6 +192,7 @@ suite('git', () => { John Doe john.doe@mail.com 1580811030 +1580811031 8e5a374372b8393906c7e380dbb09349c5385554 This is a commit message.\x00`; @@ -202,6 +203,7 @@ This is a commit message.\x00`; authorDate: new Date(1580811030000), authorName: 'John Doe', authorEmail: 'john.doe@mail.com', + commitDate: new Date(1580811031000), }]); }); @@ -210,6 +212,7 @@ This is a commit message.\x00`; John Doe john.doe@mail.com 1580811030 +1580811031 8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217 This is a commit message.\x00`; @@ -220,6 +223,7 @@ This is a commit message.\x00`; authorDate: new Date(1580811030000), authorName: 'John Doe', authorEmail: 'john.doe@mail.com', + commitDate: new Date(1580811031000), }]); }); @@ -228,6 +232,7 @@ This is a commit message.\x00`; John Doe john.doe@mail.com 1580811030 +1580811031 This is a commit message.\x00`; @@ -238,6 +243,7 @@ This is a commit message.\x00`; authorDate: new Date(1580811030000), authorName: 'John Doe', authorEmail: 'john.doe@mail.com', + commitDate: new Date(1580811031000), }]); }); }); diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 3aa36a0dccb..d6e2be8613b 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -3,20 +3,58 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dayjs from 'dayjs'; -import * as advancedFormat from 'dayjs/plugin/advancedFormat'; -import * as relativeTime from 'dayjs/plugin/relativeTime'; -import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, TimelineItem, TimelineProvider, Uri, workspace, TimelineChangeEvent } from 'vscode'; +import * as nls from 'vscode-nls'; +import { CancellationToken, Disposable, env, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; -import { Repository } from './repository'; +import { Repository, Resource } from './repository'; import { debounce } from './decorators'; -import { Status } from './api/git'; -dayjs.extend(advancedFormat); -dayjs.extend(relativeTime); +const localize = nls.loadMessageBundle(); -// TODO[ECA]: Localize all the strings -// 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; + } + + readonly ref: string; + readonly previousRef: string; + readonly message: string; + + constructor( + ref: string, + previousRef: string, + message: string, + timestamp: number, + id: string, + contextValue: string + ) { + const index = message.indexOf('\n'); + const label = index !== -1 ? `${message.substring(0, index)} \u2026` : message; + + super(label, timestamp); + + this.ref = ref; + this.previousRef = previousRef; + this.message = message; + this.id = id; + this.contextValue = contextValue; + } + + get shortRef() { + return this.shortenRef(this.ref); + } + + get shortPreviousRef() { + return this.shortenRef(this.previousRef); + } + + private shortenRef(ref: string): string { + if (ref === '' || ref === '~' || ref === 'HEAD') { + return ref; + } + return ref.endsWith('^') ? `${ref.substr(0, 8)}^` : ref.substr(0, 8); + } +} export class GitTimelineProvider implements TimelineProvider { private _onDidChange = new EventEmitter(); @@ -25,169 +63,150 @@ export class GitTimelineProvider implements TimelineProvider { } readonly id = 'git-history'; - readonly label = '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, _token: CancellationToken): Promise { + async provideTimeline(uri: Uri, options: TimelineOptions, _token: CancellationToken): Promise { // console.log(`GitTimelineProvider.provideTimeline: uri=${uri} state=${this._model.state}`); 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 []; + 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? - const commits = await repo.logFile(uri); - - let dateFormatter: dayjs.Dayjs; - const items = commits.map(c => { - let message = c.message; - - const index = message.indexOf('\n'); - if (index !== -1) { - message = `${message.substring(0, index)} \u2026`; + let limit: number | undefined; + if (options.limit !== undefined && typeof options.limit !== 'number') { + try { + const result = await this._model.git.exec(repo.root, ['rev-list', '--count', `${options.limit.id}..`, '--', uri.fsPath]); + if (!result.exitCode) { + // 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 { + limit = undefined; + } + } else { + // If we are not getting everything, ask for 1 more than so we can determine if there are more commits + limit = options.limit === undefined ? undefined : options.limit + 1; + } - dateFormatter = dayjs(c.authorDate); + const commits = await repo.logFile(uri, { + maxEntries: limit, + hash: options.cursor, + // sortByAuthorDate: true + }); - const item = new TimelineItem(message, c.authorDate?.getTime() ?? 0); - item.id = c.hash; + const paging = commits.length ? { + cursor: limit === undefined ? undefined : (commits.length >= limit ? commits[commits.length - 1]?.hash : undefined) + } : undefined; + + // If we asked for an extra commit, strip it off + if (limit !== undefined && commits.length >= limit) { + commits.splice(commits.length - 1, 1); + } + + 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 + + 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 = `${dateFormatter.fromNow()} \u2022 ${c.authorName}`; - item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n\n${c.message}`; + item.description = c.authorName; + item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format(date)}\n\n${c.message}`; item.command = { - title: 'Open Diff', - command: 'git.openDiff', - arguments: [uri, `${c.hash}^`, c.hash] + title: 'Open Comparison', + command: 'git.timeline.openDiff', + arguments: [item, uri, this.id] }; return item; }); - const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); - if (index) { - const date = this._repoStatusDate ?? new Date(); - dateFormatter = dayjs(date); + if (options.cursor === undefined) { + const you = localize('git.timeline.you', 'You'); - let status; - switch (index.type) { - case Status.INDEX_MODIFIED: - status = 'Modified'; - break; - case Status.INDEX_ADDED: - status = 'Added'; - break; - case Status.INDEX_DELETED: - status = 'Deleted'; - break; - case Status.INDEX_RENAMED: - status = 'Renamed'; - break; - case Status.INDEX_COPIED: - status = 'Copied'; - break; - default: - status = ''; - break; + const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); + if (index) { + const date = this.repoStatusDate ?? new Date(); + + const item = new GitTimelineItem('~', 'HEAD', localize('git.timeline.stagedChanges', 'Staged Changes'), date.getTime(), 'index', 'git:file:index'); + // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? + item.iconPath = new (ThemeIcon as any)('git-commit'); + 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', + arguments: [item, uri, this.id] + }; + + items.splice(0, 0, item); } - const item = new TimelineItem('Staged Changes', date.getTime()); - item.id = 'index'; - // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? - item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = `${dateFormatter.fromNow()} \u2022 You`; - item.detail = `You \u2014 Index\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`; - item.command = { - title: 'Open Comparison', - command: 'git.openDiff', - arguments: [uri, 'HEAD', '~'] - }; + const working = repo.workingTreeGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); + if (working) { + const date = new Date(); - items.push(item); - } + const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommited Changes'), date.getTime(), 'working', 'git:file:working'); + // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? + item.iconPath = new (ThemeIcon as any)('git-commit'); + 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', + arguments: [item, uri, this.id] + }; - - const working = repo.workingTreeGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); - if (working) { - const date = new Date(); - dateFormatter = dayjs(date); - - let status; - switch (working.type) { - case Status.INDEX_MODIFIED: - status = 'Modified'; - break; - case Status.INDEX_ADDED: - status = 'Added'; - break; - case Status.INDEX_DELETED: - status = 'Deleted'; - break; - case Status.INDEX_RENAMED: - status = 'Renamed'; - break; - case Status.INDEX_COPIED: - status = 'Copied'; - break; - default: - status = ''; - break; + items.splice(0, 0, item); } - - const item = new TimelineItem('Uncommited Changes', date.getTime()); - item.id = 'working'; - // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? - item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = `${dateFormatter.fromNow()} \u2022 You`; - item.detail = `You \u2014 Working Tree\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`; - item.command = { - title: 'Open Comparison', - command: 'git.openDiff', - arguments: [uri, index ? '~' : 'HEAD', ''] - }; - - items.push(item); } - return items; + return { + items: items, + paging: paging + }; } 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(); } @@ -201,13 +220,13 @@ 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(); } @debounce(500) private fireChanged() { - this._onDidChange.fire(); + this._onDidChange.fire(undefined); } } diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index af5b10127dc..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" @@ -382,7 +377,7 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: +har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== @@ -697,17 +692,12 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -psl@^1.1.24: +psl@^1.1.28: version "1.7.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -723,9 +713,9 @@ querystringify@^2.1.1: integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -734,7 +724,7 @@ request@^2.88.0: extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.0" + har-validator "~5.1.3" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" @@ -744,7 +734,7 @@ request@^2.88.0: performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" - tough-cookie "~2.4.3" + tough-cookie "~2.5.0" tunnel-agent "^0.6.0" uuid "^3.3.2" @@ -822,13 +812,13 @@ supports-color@5.4.0: dependencies: has-flag "^3.0.0" -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: - psl "^1.1.24" - punycode "^1.4.1" + psl "^1.1.28" + punycode "^2.1.1" tunnel-agent@^0.6.0: version "0.6.0" diff --git a/extensions/github-authentication/.gitignore b/extensions/github-authentication/.gitignore new file mode 100644 index 00000000000..eab338cd463 --- /dev/null +++ b/extensions/github-authentication/.gitignore @@ -0,0 +1 @@ +src/common/config.json diff --git a/extensions/github-authentication/README.md b/extensions/github-authentication/README.md new file mode 100644 index 00000000000..755e5020961 --- /dev/null +++ b/extensions/github-authentication/README.md @@ -0,0 +1,7 @@ +# GitHub Authentication for Visual Studio Code + +**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +## Features + +This extension provides support for authenticating to GitHub. diff --git a/extensions/github-authentication/build/postinstall.js b/extensions/github-authentication/build/postinstall.js new file mode 100644 index 00000000000..2f311efc223 --- /dev/null +++ b/extensions/github-authentication/build/postinstall.js @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const fs = require('fs'); +const path = require('path'); + +const schemes = ['OSS', 'INSIDERS', 'STABLE', 'EXPLORATION', 'VSO', 'VSO_PPE', 'VSO_DEV']; + +function main() { + let content = {}; + + for (const scheme of schemes) { + const id = process.env[`${scheme}_GITHUB_ID`]; + const secret = process.env[`${scheme}_GITHUB_SECRET`]; + + if (id && secret) { + content[scheme] = { id, secret }; + } + } + + 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/extension.webpack.config.js b/extensions/github-authentication/extension.webpack.config.js new file mode 100644 index 00000000000..a513ac5c3b5 --- /dev/null +++ b/extensions/github-authentication/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts', + }, + externals: { + 'keytar': 'commonjs keytar' + } +}); diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json new file mode 100644 index 00000000000..bf2d01cbe3f --- /dev/null +++ b/extensions/github-authentication/package.json @@ -0,0 +1,34 @@ +{ + "name": "github-authentication", + "displayName": "%displayName%", + "description": "%description%", + "publisher": "vscode", + "version": "0.0.1", + "engines": { + "vscode": "^1.41.0" + }, + "enableProposedApi": true, + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "main": "./out/extension.js", + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "gulp compile-extension:github-authentication", + "watch": "gulp watch-extension:github-authentication", + "postinstall": "node build/postinstall.js" + }, + "dependencies": { + "uuid": "^3.3.3", + "vscode-nls": "^4.1.2" + }, + "devDependencies": { + "@types/keytar": "^4.4.2", + "@types/node": "^10.12.21", + "@types/uuid": "^3.4.6", + "typescript": "^3.7.5" + } +} diff --git a/extensions/github-authentication/package.nls.json b/extensions/github-authentication/package.nls.json new file mode 100644 index 00000000000..592a413b9a5 --- /dev/null +++ b/extensions/github-authentication/package.nls.json @@ -0,0 +1,4 @@ +{ + "displayName": "GitHub Authentication", + "description": "GitHub Authentication Provider" +} diff --git a/extensions/github-authentication/src/common/clientRegistrar.ts b/extensions/github-authentication/src/common/clientRegistrar.ts new file mode 100644 index 00000000000..4666ec6a6c0 --- /dev/null +++ b/extensions/github-authentication/src/common/clientRegistrar.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Uri, env } from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; + +export interface ClientDetails { + id?: string; + secret?: string; +} + +export interface ClientConfig { + OSS: ClientDetails; + INSIDERS: ClientDetails; + STABLE: ClientDetails; + EXPLORATION: ClientDetails; + + VSO: ClientDetails; + VSO_PPE: ClientDetails; + VSO_DEV: ClientDetails; + + GITHUB_APP: ClientDetails; +} + +export class Registrar { + private _config: ClientConfig; + + constructor() { + try { + 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: {}, + INSIDERS: {}, + STABLE: {}, + EXPLORATION: {}, + VSO: {}, + VSO_PPE: {}, + 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) { + case 'code-oss': + details = this._config.OSS; + break; + + case 'vscode-insiders': + details = this._config.INSIDERS; + break; + + case 'vscode': + details = this._config.STABLE; + break; + + case 'vscode-exploration': + details = this._config.EXPLORATION; + break; + + case 'https': + switch (callbackUri.authority) { + case 'online.visualstudio.com': + details = this._config.VSO; + break; + case 'online-ppe.core.vsengsaas.visualstudio.com': + details = this._config.VSO_PPE; + break; + case 'online.dev.core.vsengsaas.visualstudio.com': + details = this._config.VSO_DEV; + break; + } + + default: + throw new Error(`Unrecognized callback ${callbackUri}`); + } + + if (!details.id || !details.secret) { + throw new Error(`No client configuration available for ${callbackUri}`); + } + + return details; + } +} + +const ClientRegistrar = new Registrar(); +export default ClientRegistrar; diff --git a/extensions/github-authentication/src/common/keychain.ts b/extensions/github-authentication/src/common/keychain.ts new file mode 100644 index 00000000000..9a3c0c662d2 --- /dev/null +++ b/extensions/github-authentication/src/common/keychain.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. + *--------------------------------------------------------------------------------------------*/ + +// keytar depends on a native module shipped in vscode, so this is +// how we load it +import * as keytarType from 'keytar'; +import * as vscode from 'vscode'; +import Logger from './logger'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + +function getKeytar(): Keytar | undefined { + try { + return require('keytar'); + } catch (err) { + console.log(err); + } + + return undefined; +} + +export type Keytar = { + getPassword: typeof keytarType['getPassword']; + setPassword: typeof keytarType['setPassword']; + deletePassword: typeof keytarType['deletePassword']; +}; + +const SERVICE_ID = `${vscode.env.uriScheme}-github.login`; +const ACCOUNT_ID = 'account'; + +export class Keychain { + private keytar: Keytar; + + constructor() { + const keytar = getKeytar(); + if (!keytar) { + throw new Error('System keychain unavailable'); + } + + this.keytar = keytar; + } + + async setToken(token: string): Promise { + try { + 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 { + try { + return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID); + } catch (e) { + // Ignore + Logger.error(`Getting token failed: ${e}`); + return Promise.resolve(undefined); + } + } + + async deleteToken(): Promise { + try { + return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID); + } catch (e) { + // Ignore + Logger.error(`Deleting token failed: ${e}`); + return Promise.resolve(undefined); + } + } +} + +export const keychain = new Keychain(); diff --git a/extensions/github-authentication/src/common/logger.ts b/extensions/github-authentication/src/common/logger.ts new file mode 100644 index 00000000000..90b3be8c97c --- /dev/null +++ b/extensions/github-authentication/src/common/logger.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 * as vscode from 'vscode'; + +type LogLevel = 'Trace' | 'Info' | 'Error'; + +class Log { + private output: vscode.OutputChannel; + + constructor() { + this.output = vscode.window.createOutputChannel('GitHub Authentication'); + } + + private data2String(data: any): string { + if (data instanceof Error) { + return data.stack || data.message; + } + if (data.success === false && data.message) { + return data.message; + } + return data.toString(); + } + + public info(message: string, data?: any): void { + this.logLevel('Info', message, data); + } + + public error(message: string, data?: any): void { + this.logLevel('Error', message, data); + } + + public logLevel(level: LogLevel, message: string, data?: any): void { + this.output.appendLine(`[${level} - ${this.now()}] ${message}`); + if (data) { + this.output.appendLine(this.data2String(data)); + } + } + + private now(): string { + const now = new Date(); + return padLeft(now.getUTCHours() + '', 2, '0') + + ':' + padLeft(now.getMinutes() + '', 2, '0') + + ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds(); + } +} + +function padLeft(s: string, n: number, pad = ' ') { + return pad.repeat(Math.max(0, n - s.length)) + s; +} + +const Logger = new Log(); +export default Logger; diff --git a/extensions/github-authentication/src/common/utils.ts b/extensions/github-authentication/src/common/utils.ts new file mode 100644 index 00000000000..6b18a9221db --- /dev/null +++ b/extensions/github-authentication/src/common/utils.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Disposable } from 'vscode'; + +export function filterEvent(event: Event, filter: (e: T) => boolean): Event { + return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); +} + +export function onceEvent(event: Event): Event { + return (listener, thisArgs = null, disposables?) => { + const result = event(e => { + result.dispose(); + return listener.call(thisArgs, e); + }, null, disposables); + + return result; + }; +} + + +export interface PromiseAdapter { + ( + value: T, + resolve: + (value?: U | PromiseLike) => void, + reject: + (reason: any) => void + ): any; +} + +const passthrough = (value: any, resolve: (value?: any) => void) => resolve(value); + +/** + * Return a promise that resolves with the next emitted event, or with some future + * event as decided by an adapter. + * + * If specified, the adapter is a function that will be called with + * `(event, resolve, reject)`. It will be called once per event until it resolves or + * rejects. + * + * The default adapter is the passthrough function `(value, resolve) => resolve(value)`. + * + * @param event the event + * @param adapter controls resolution of the returned promise + * @returns a promise that resolves or rejects as specified by the adapter + */ +export async function promiseFromEvent( + event: Event, + adapter: PromiseAdapter = passthrough): Promise { + let subscription: Disposable; + return new Promise((resolve, reject) => + subscription = event((value: T) => { + try { + Promise.resolve(adapter(value, resolve, reject)) + .catch(reject); + } catch (error) { + reject(error); + } + }) + ).then( + (result: U) => { + subscription.dispose(); + return result; + }, + error => { + subscription.dispose(); + throw error; + } + ); +} diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts new file mode 100644 index 00000000000..dee2f40bafa --- /dev/null +++ b/extensions/github-authentication/src/extension.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 * as vscode from 'vscode'; +import { GitHubAuthenticationProvider, onDidChangeSessions } from './github'; +import { uriHandler } from './githubServer'; +import Logger from './common/logger'; + +export async function activate(context: vscode.ExtensionContext) { + + context.subscriptions.push(vscode.window.registerUriHandler(uriHandler)); + const loginService = new GitHubAuthenticationProvider(); + + await loginService.initialize(); + + vscode.authentication.registerAuthenticationProvider({ + id: 'github', + displayName: 'GitHub', + onDidChangeSessions: onDidChangeSessions.event, + getSessions: () => Promise.resolve(loginService.sessions), + login: async (scopeList: string[]) => { + try { + 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}`); + Logger.error(e); + throw e; + } + }, + logout: async (id: string) => { + await loginService.logout(id); + onDidChangeSessions.fire({ added: [], removed: [id], changed: [] }); + } + }); + + return; +} + +// this method is called when your extension is deactivated +export function deactivate() { } diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts new file mode 100644 index 00000000000..6fbe82516ac --- /dev/null +++ b/extensions/github-authentication/src/github.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * 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 uuid from 'uuid'; +import { keychain } from './common/keychain'; +import { GitHubServer } from './githubServer'; +import Logger from './common/logger'; + +export const onDidChangeSessions = new vscode.EventEmitter(); + +interface SessionData { + id: string; + accountName: string; + scopes: string[]; + accessToken: string; +} + +export class GitHubAuthenticationProvider { + private _sessions: vscode.AuthenticationSession[] = []; + private _githubServer = new GitHubServer(); + + public async initialize(): Promise { + this._sessions = await this.readSessions(); + this.pollForChange(); + } + + private pollForChange() { + setTimeout(async () => { + const storedSessions = await this.readSessions(); + + 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); + added.push(session.id); + } + }); + + this._sessions.map(session => { + const matchesExisting = storedSessions.some(s => s.id === session.id); + // Another window has logged out, remove from our state + if (!matchesExisting) { + const sessionIndex = this._sessions.findIndex(s => s.id === session.id); + if (sessionIndex > -1) { + this._sessions.splice(sessionIndex, 1); + } + + removed.push(session.id); + } + }); + + if (added.length || removed.length) { + onDidChangeSessions.fire({ added, removed, changed: [] }); + } + + this.pollForChange(); + }, 1000 * 30); + } + + private async readSessions(): Promise { + const storedSessions = await keychain.getToken(); + if (storedSessions) { + try { + const sessionData: SessionData[] = JSON.parse(storedSessions); + return sessionData.map(session => { + return { + id: session.id, + accountName: session.accountName, + scopes: session.scopes, + getAccessToken: () => Promise.resolve(session.accessToken) + }; + }); + } catch (e) { + Logger.error(`Error reading sessions: ${e}`); + } + } + + return []; + } + + private async storeSessions(): Promise { + const sessionData: SessionData[] = await Promise.all(this._sessions.map(async session => { + const resolvedAccessToken = await session.getAccessToken(); + return { + id: session.id, + accountName: session.accountName, + scopes: session.scopes, + accessToken: resolvedAccessToken + }; + })); + + await keychain.setToken(JSON.stringify(sessionData)); + } + + get sessions(): vscode.AuthenticationSession[] { + return this._sessions; + } + + public async login(scopes: string): Promise { + 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: uuid(), + getAccessToken: () => Promise.resolve(token), + accountName: userInfo.accountName, + scopes: scopes + }; + } + private async setToken(session: vscode.AuthenticationSession): Promise { + const sessionIndex = this._sessions.findIndex(s => s.id === session.id); + if (sessionIndex > -1) { + this._sessions.splice(sessionIndex, 1, session); + } else { + this._sessions.push(session); + } + + this.storeSessions(); + } + + public async logout(id: string) { + const sessionIndex = this._sessions.findIndex(session => session.id === id); + if (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 new file mode 100644 index 00000000000..5f4d3dfa8c3 --- /dev/null +++ b/extensions/github-authentication/src/githubServer.ts @@ -0,0 +1,201 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as https from 'https'; +import * as vscode from 'vscode'; +import * as uuid from 'uuid'; +import { PromiseAdapter, promiseFromEvent } from './common/utils'; +import Logger from './common/logger'; +import ClientRegistrar, { ClientDetails } from './common/clientRegistrar'; + +class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { + public handleUri(uri: vscode.Uri) { + this.fire(uri); + } +} + +export const uriHandler = new UriEventHandler; + +const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => PromiseAdapter = + (state, clientDetails) => async (uri, resolve, reject) => { + Logger.info('Exchanging code for token...'); + const query = parseQuery(uri); + const code = query.code; + + if (query.state !== state) { + reject('Received mismatched state'); + return; + } + + const post = https.request({ + host: 'github.com', + path: `/login/oauth/access_token?client_id=${clientDetails.id}&client_secret=${clientDetails.secret}&state=${query.state}&code=${code}`, + method: 'POST', + headers: { + Accept: 'application/json' + } + }, 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('Token exchange success!'); + resolve(json.access_token); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.end(); + post.on('error', err => { + reject(err); + }); + }; + +function parseQuery(uri: vscode.Uri) { + return uri.query.split('&').reduce((prev: any, current) => { + const queryString = current.split('='); + prev[queryString[0]] = queryString[1]; + return prev; + }, {}); +} + +export class GitHubServer { + public async login(scopes: string): Promise { + 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 = 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...'); + const post = https.request({ + host: 'api.github.com', + path: `/user`, + method: 'GET', + headers: { + 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 account info!'); + resolve({ id: json.id, accountName: json.login }); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.end(); + post.on('error', err => { + reject(err); + }); + }); + } + + 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/src/vs/editor/standalone/browser/quickOpen/gotoLine.css b/extensions/github-authentication/src/typings/ref.d.ts similarity index 74% rename from src/vs/editor/standalone/browser/quickOpen/gotoLine.css rename to extensions/github-authentication/src/typings/ref.d.ts index e71a5e1dd76..c9849d48e08 100644 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.css +++ b/extensions/github-authentication/src/typings/ref.d.ts @@ -3,6 +3,5 @@ * 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 +/// +/// diff --git a/extensions/github-authentication/tsconfig.json b/extensions/github-authentication/tsconfig.json new file mode 100644 index 00000000000..1225709307b --- /dev/null +++ b/extensions/github-authentication/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../shared.tsconfig.json", + "compilerOptions": { + "outDir": "./out", + "experimentalDecorators": true, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock new file mode 100644 index 00000000000..c1f0b96f5b5 --- /dev/null +++ b/extensions/github-authentication/yarn.lock @@ -0,0 +1,459 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/keytar@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.2.tgz#49ef917d6cbb4f19241c0ab50cd35097b5729b32" + integrity sha512-xtQcDj9ruGnMwvSu1E2BH4SFa5Dv2PvSPd0CKEBLN5hEj/v5YpXJY+B6hAfuKIbvEomD7vJTc/P1s1xPNh2kRw== + dependencies: + keytar "*" + +"@types/node@^10.12.21": + version "10.17.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.14.tgz#b6c60ebf2fb5e4229fdd751ff9ddfae0f5f31541" + integrity sha512-G0UmX5uKEmW+ZAhmZ6PLTQ5eu/VPaT+d/tdLd5IFsKRPcbe6lPxocBtcYBFSaLaCW8O60AX90e91Nsp8lVHCNw== + +"@types/uuid@^3.4.6": + version "3.4.7" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.7.tgz#51d42247473bc00e38cc8dfaf70d936842a36c03" + integrity sha512-C2j2FWgQkF1ru12SjZJyMaTPxs/f6n90+5G5qNakBxKXjTBc/YTSelHh4Pz1HUDwxFXD9WvpQhOGCDC+/Y4mIQ== + +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" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +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" + +bl@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" + integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + dependencies: + readable-stream "^3.0.1" + +chownr@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" + integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== + +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= + +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= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +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" + +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== + +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.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +end-of-stream@^1.1.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" + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +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" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + +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= + +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== + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +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" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +keytar@*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.1.0.tgz#d572ed9250ff2b4c8d729621397e00b17bfa5581" + integrity sha512-SptCrRDqLbTeOMB2Z9UmVOS+OKguIrMft+EUaCB8xJPiFMjy6Jnmjgv/LA0rg1ENgLelzwSsC5PSQXF0uoqNDQ== + dependencies: + nan "2.14.0" + prebuild-install "5.3.3" + +mimic-response@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" + integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== + +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.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +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" + +nan@2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +napi-build-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" + integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== + +node-abi@^2.7.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.14.0.tgz#24650e24e8ffad2b61352519263f0cf4e2ddbfe9" + integrity sha512-y54KGgEOHnRHlGQi7E5UiryRkH8bmksmQLj/9iLAjoje743YS+KaKB/sDYXgqtT0J16JT3c3AYJZNI98aU/kYg== + dependencies: + semver "^5.4.1" + +noop-logger@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= + +npmlog@^4.0.1: + 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= + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +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" + github-from-package "0.0.0" + minimist "^1.2.0" + mkdirp "^0.5.1" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + +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" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.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== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.0.6: + 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" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.1, readable-stream@^3.1.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606" + integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +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.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== + +semver@^5.4.1: + 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: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +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= + +simple-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" + integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + +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 "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.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" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +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" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +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" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +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= + +tar-fs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== + dependencies: + chownr "^1.1.1" + mkdirp "^0.5.1" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" + integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== + dependencies: + bl "^3.0.0" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +typescript@^3.7.5: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + +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= + +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.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" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/extensions/groovy/package.json b/extensions/groovy/package.json index 9bed5780419..55da6a97fc8 100644 --- a/extensions/groovy/package.json +++ b/extensions/groovy/package.json @@ -25,7 +25,7 @@ }], "snippets": [{ "language": "groovy", - "path": "./snippets/groovy.json" + "path": "./snippets/groovy.code-snippets" }] } } diff --git a/extensions/groovy/snippets/groovy.json b/extensions/groovy/snippets/groovy.code-snippets similarity index 100% rename from extensions/groovy/snippets/groovy.json rename to extensions/groovy/snippets/groovy.code-snippets 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 526f54fcacb..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", @@ -201,7 +202,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.1.0", + "vscode-languageclient": "^6.1.1", "vscode-nls": "^4.1.1" }, "devDependencies": { 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/package.json b/extensions/html-language-features/server/package.json index 0083f2b1bd5..5064259bfbf 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,14 +9,14 @@ }, "main": "./out/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.3-next.27", - "vscode-html-languageservice": "^3.0.4-next.14", - "vscode-languageserver": "^6.1.0", + "vscode-css-languageservice": "^4.1.0", + "vscode-html-languageservice": "3.0.4-next.15", + "vscode-languageserver": "^6.1.1", "vscode-nls": "^4.1.1", "vscode-uri": "^2.1.1" }, "devDependencies": { - "@types/mocha": "2.2.33", + "@types/mocha": "7.0.1", "@types/node": "^12.11.7", "glob": "^7.1.6", "mocha": "^7.0.1", 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/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index b2659f1c92d..f4ed7ba600d 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/mocha@2.2.33": - version "2.2.33" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.33.tgz#d79a0061ec270379f4d9e225f4096fb436669def" - integrity sha1-15oAYewnA3n02eIl9AlvtDZmne8= +"@types/mocha@7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e" + integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q== "@types/node@^12.11.7": version "12.11.7" @@ -714,20 +714,20 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-css-languageservice@^4.0.3-next.27: - version "4.0.3-next.27" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.27.tgz#04f52bfdd85ac7eed8d82a0133aaabb4c0e18f7f" - integrity sha512-MU8sHQABb1WnzOwgazIdO4lXG7cGzFKd7VKi5j67uWTNsqSrbAVSoKGjSyOLq/o6h1L5DGG1Og/7q403z6D04g== +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== dependencies: - vscode-languageserver-textdocument "^1.0.1-next.1" + vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.15.1" vscode-nls "^4.1.1" vscode-uri "^2.1.1" -vscode-html-languageservice@^3.0.4-next.14: - version "3.0.4-next.14" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.4-next.14.tgz#143cda8d1b032da3506de2ee7c352f50159edc7f" - integrity sha512-YQ1nYy3Yqai+mrvORFeJW1/CvkBd9y2kYDOvfTEmt5uCd7+7iMTQLdhPFWwlx/GKDXuvQJoJ0E3eNd/gGb3cvQ== +vscode-html-languageservice@3.0.4-next.15: + version "3.0.4-next.15" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.4-next.15.tgz#7214ccd9b4a06cf138b5945d9fd88285a0add490" + integrity sha512-UmUm3A1ZTj+BloVIyel+5pK/nfsqRfPLXzl8BA9O7v5Cj64vivddABvNf/rW1US8fzdikFNZNloC/4ooqxB2kw== dependencies: vscode-languageserver-textdocument "^1.0.1-next.1" vscode-languageserver-types "^3.15.1" @@ -739,14 +739,19 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageserver-protocol@^3.15.2: - version "3.15.2" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.2.tgz#e52c62923140b2655ad2472f6f29cfb83bacf5b8" - integrity sha512-GdL05JKOgZ76RDg3suiGCl9enESM7iQgGw4x93ibTh4sldvZmakHmTeZ4iUApPPGKf6O3OVBtrsksBXnHYaxNg== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" vscode-languageserver-types "3.15.1" +vscode-languageserver-textdocument@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f" + integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA== + vscode-languageserver-textdocument@^1.0.1-next.1: version "1.0.1-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1-next.1.tgz#c8f2f792c7c88d33ea8441ca04bfb8376896b671" @@ -757,12 +762,12 @@ vscode-languageserver-types@3.15.1, vscode-languageserver-types@^3.15.1: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== -vscode-languageserver@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.0.tgz#f0ff149b06d1961f49ed03385ecc2a96bcaddcde" - integrity sha512-Q5kUJegYclTZMnKUaEcxJK41Ozp6qJhhoFJYj0w8y8j9JXdKT479LE945QCKRvSgWfsqTSUmgsozVTUIwQQxHw== +vscode-languageserver@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz#d76afc68172c27d4327ee74332b468fbc740d762" + integrity sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ== dependencies: - vscode-languageserver-protocol "^3.15.2" + vscode-languageserver-protocol "^3.15.3" vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index e91ad63907d..8dfd3e7e9d0 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -50,18 +50,18 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageclient@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.0.tgz#ee67c0b7818c42ce0281572d05c89adfcc4f5a38" - integrity sha512-Tcp0VoOaa0YzxL4nEfK9tsmcy76Eo8jNLvFQZwh2c8oMm02luL8uGYPLQNAiZ3XGgegfcwiQFZMqbW7DNV0vxA== +vscode-languageclient@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.1.tgz#91b62e416c5abbf2013ae3726f314a19c22a8457" + integrity sha512-mB6d8Tg+82l8EFUfR+SBu0+lCshyKVgC5E5+MQ0/BJa+9AgeBjtG5npoGaCo4/VvWzK0ZRGm85zU5iRp1RYPIA== dependencies: semver "^6.3.0" - vscode-languageserver-protocol "^3.15.2" + vscode-languageserver-protocol "^3.15.3" -vscode-languageserver-protocol@^3.15.2: - version "3.15.2" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.2.tgz#e52c62923140b2655ad2472f6f29cfb83bacf5b8" - integrity sha512-GdL05JKOgZ76RDg3suiGCl9enESM7iQgGw4x93ibTh4sldvZmakHmTeZ4iUApPPGKf6O3OVBtrsksBXnHYaxNg== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" vscode-languageserver-types "3.15.1" diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index a0664f98e3d..48c7ae314a9 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -17,15 +17,15 @@ "Other" ], "activationEvents": [ - "onWebviewEditor:imagePreview.previewEditor", + "onCustomEditor:imagePreview.previewEditor", "onCommand:imagePreview.zoomIn", "onCommand:imagePreview.zoomOut" ], "contributes": { - "webviewEditors": [ + "customEditors": [ { "viewType": "imagePreview.previewEditor", - "displayName": "%webviewEditors.displayName%", + "displayName": "%customEditors.displayName%", "priority": "builtin", "selector": [ { diff --git a/extensions/image-preview/package.nls.json b/extensions/image-preview/package.nls.json index 304b1df9a3d..d1860bc2fb5 100644 --- a/extensions/image-preview/package.nls.json +++ b/extensions/image-preview/package.nls.json @@ -1,7 +1,7 @@ { "displayName": "Image Preview", "description": "Provides VS Code's built-in image preview", - "webviewEditors.displayName": "Image Preview", + "customEditors.displayName": "Image Preview", "command.zoomIn": "Zoom in", "command.zoomOut": "Zoom out" } diff --git a/extensions/image-preview/src/extension.ts b/extensions/image-preview/src/extension.ts index dc0d5c33956..45598581896 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.registerWebviewCustomEditorProvider(PreviewManager.viewType, previewManager)); + context.subscriptions.push(vscode.window.registerCustomEditorProvider2(PreviewManager.viewType, previewManager)); context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => { previewManager.activePreview?.zoomIn(); @@ -33,4 +33,3 @@ export function activate(context: vscode.ExtensionContext) { previewManager.activePreview?.zoomOut(); })); } - diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index 5e733b13cc2..ebe5653513a 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -13,7 +13,7 @@ import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; const localize = nls.loadMessageBundle(); -export class PreviewManager implements vscode.WebviewCustomEditorProvider { +export class PreviewManager implements vscode.CustomEditorProvider { public static readonly viewType = 'imagePreview.previewEditor'; @@ -27,11 +27,15 @@ export class PreviewManager implements vscode.WebviewCustomEditorProvider { private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { } - public async resolveWebviewEditor( - resource: vscode.Uri, + public async openCustomDocument(uri: vscode.Uri) { + return { uri, dispose: () => { } }; + } + + public async resolveCustomEditor( + document: vscode.CustomDocument, webviewEditor: vscode.WebviewPanel, ): Promise { - const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); + const preview = new Preview(this.extensionRoot, document.uri, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); this._previews.add(preview); this.setActivePreview(preview); diff --git a/extensions/java/cgmanifest.json b/extensions/java/cgmanifest.json index b7090d4a45c..7f0ef8760a8 100644 --- a/extensions/java/cgmanifest.json +++ b/extensions/java/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "atom/language-java", "repositoryUrl": "https://github.com/atom/language-java", - "commitHash": "0facf7cbe02cda460db1160fd730f2e57bf15c36" + "commitHash": "2e179ceac423403eb5bf0eff26884093c3edba6f" } }, "license": "MIT", diff --git a/extensions/java/package.json b/extensions/java/package.json index 9d3310681b9..96f8c8c4adf 100644 --- a/extensions/java/package.json +++ b/extensions/java/package.json @@ -23,7 +23,7 @@ }], "snippets": [{ "language": "java", - "path": "./snippets/java.snippets.json" + "path": "./snippets/java.code-snippets" }] } -} \ No newline at end of file +} diff --git a/extensions/java/snippets/java.snippets.json b/extensions/java/snippets/java.code-snippets similarity index 100% rename from extensions/java/snippets/java.snippets.json rename to extensions/java/snippets/java.code-snippets diff --git a/extensions/java/syntaxes/java.tmLanguage.json b/extensions/java/syntaxes/java.tmLanguage.json index 7dad7d72837..3cfa6f56337 100644 --- a/extensions/java/syntaxes/java.tmLanguage.json +++ b/extensions/java/syntaxes/java.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-java/commit/0facf7cbe02cda460db1160fd730f2e57bf15c36", + "version": "https://github.com/atom/language-java/commit/2e179ceac423403eb5bf0eff26884093c3edba6f", "name": "Java", "scopeName": "source.java", "patterns": [ @@ -221,22 +221,60 @@ "include": "#all-types" }, { - "begin": "(?)?(\\()", + "beginCaptures": { + "1": { + "name": "storage.modifier.java" + }, + "2": { + "name": "entity.name.type.record.java" + }, + "3": { + "patterns": [ + { + "include": "#generics" + } + ] + }, + "4": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.record.identifier.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "begin": "(implements)\\s", + "beginCaptures": { + "1": { + "name": "storage.modifier.implements.java" + } + }, + "end": "(?=\\s*\\{)", + "name": "meta.definition.class.implemented.interfaces.java", + "patterns": [ + { + "include": "#object-types-inherited" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#record-body" + } + ] + }, + "record-body": { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.class.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "name": "meta.record.body.java", + "patterns": [ + { + "include": "#record-constructor" + }, + { + "include": "#class-body" + } + ] + }, + "record-constructor": { + "begin": "(?!new)(?=[\\w<].*\\s+)(?=([^\\(=/]|/(?!/))+(?={))", + "end": "(})|(?=;)", + "endCaptures": { + "1": { + "name": "punctuation.section.method.end.bracket.curly.java" + } + }, + "name": "meta.method.java", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "begin": "(\\w+)", + "beginCaptures": { + "1": { + "name": "entity.name.function.java" + } + }, + "end": "(?=\\s*{)", + "name": "meta.method.identifier.java", + "patterns": [ + { + "include": "#comments" + } + ] + }, + { + "include": "#comments" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.method.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.method.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, "static-initializer": { "patterns": [ { diff --git a/extensions/javascript/package.json b/extensions/javascript/package.json index b7303520549..d63bebded16 100644 --- a/extensions/javascript/package.json +++ b/extensions/javascript/package.json @@ -90,14 +90,42 @@ "path": "./syntaxes/Regular Expressions (JavaScript).tmLanguage" } ], - "snippets": [ + "semanticTokenScopes": [ { "language": "javascript", - "path": "./snippets/javascript.json" + "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", - "path": "./snippets/javascript.json" + "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", + "path": "./snippets/javascript.code-snippets" + }, + { + "language": "javascriptreact", + "path": "./snippets/javascript.code-snippets" } ] } diff --git a/extensions/javascript/snippets/javascript.json b/extensions/javascript/snippets/javascript.code-snippets similarity index 100% rename from extensions/javascript/snippets/javascript.json rename to extensions/javascript/snippets/javascript.code-snippets 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 b52141b947c..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 { @@ -76,29 +81,29 @@ let telemetryReporter: TelemetryReporter | undefined; export function activate(context: ExtensionContext) { - let toDispose = context.subscriptions; + const toDispose = context.subscriptions; let rangeFormatting: Disposable | undefined = undefined; - let packageInfo = getPackageInfo(context); + const packageInfo = getPackageInfo(context); telemetryReporter = packageInfo && new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey); - let serverMain = readJSONFile(context.asAbsolutePath('./server/package.json')).main; - let serverModule = context.asAbsolutePath(path.join('server', serverMain)); + const serverMain = readJSONFile(context.asAbsolutePath('./server/package.json')).main; + const serverModule = context.asAbsolutePath(path.join('server', serverMain)); // The debug options for the server - let debugOptions = { execArgv: ['--nolazy', '--inspect=' + (9000 + Math.round(Math.random() * 10000))] }; + const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (9000 + Math.round(Math.random() * 10000))] }; // If the extension is launch in debug mode the debug server options are use // Otherwise the run options are used - let serverOptions: ServerOptions = { + const serverOptions: ServerOptions = { run: { module: serverModule, transport: TransportKind.ipc }, debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } }; - let documentSelector = ['json', 'jsonc']; + const documentSelector = ['json', 'jsonc']; - let schemaResolutionErrorStatusBarItem = window.createStatusBarItem({ + const schemaResolutionErrorStatusBarItem = window.createStatusBarItem({ id: 'status.json.resolveError', name: localize('json.resolveError', "JSON: Schema Resolution Error"), alignment: StatusBarAlignment.Right, @@ -109,10 +114,10 @@ export function activate(context: ExtensionContext) { schemaResolutionErrorStatusBarItem.text = '$(alert)'; toDispose.push(schemaResolutionErrorStatusBarItem); - let fileSchemaErrors = new Map(); + const fileSchemaErrors = new Map(); // Options to control the language client - let clientOptions: LanguageClientOptions = { + const clientOptions: LanguageClientOptions = { // Register the server for json documents documentSelector, initializationOptions: { @@ -172,17 +177,20 @@ export function activate(context: ExtensionContext) { }; // Create the language client and start the client. - let client = new LanguageClient('json', localize('jsonserver.name', 'JSON Language Server'), serverOptions, clientOptions); + const client = new LanguageClient('json', localize('jsonserver.name', 'JSON Language Server'), serverOptions, clientOptions); client.registerProposedFeatures(); - let disposable = client.start(); + const disposable = client.start(); toDispose.push(disposable); client.onReady().then(() => { const schemaDocuments: { [uri: string]: boolean } = {}; // handle content request client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { - let uri = Uri.parse(uriPath); + 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; @@ -212,7 +220,7 @@ export function activate(context: ExtensionContext) { } }); - let handleContentChange = (uriString: string) => { + const handleContentChange = (uriString: string) => { if (schemaDocuments[uriString]) { client.sendNotification(SchemaContentChangeNotification.type, uriString); return true; @@ -220,7 +228,7 @@ export function activate(context: ExtensionContext) { return false; }; - let handleActiveEditorChange = (activeEditor?: TextEditor) => { + const handleActiveEditorChange = (activeEditor?: TextEditor) => { if (!activeEditor) { return; } @@ -244,7 +252,7 @@ export function activate(context: ExtensionContext) { })); toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange)); - let handleRetryResolveSchemaCommand = () => { + const handleRetryResolveSchemaCommand = () => { if (window.activeTextEditor) { schemaResolutionErrorStatusBarItem.text = '$(watch)'; const activeDocUri = window.activeTextEditor.document.uri.toString(); @@ -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. @@ -282,7 +290,7 @@ export function activate(context: ExtensionContext) { }); - let languageConfiguration: LanguageConfiguration = { + const languageConfiguration: LanguageConfiguration = { wordPattern: /("(?:[^\\\"]*(?:\\.)?)*"?)|[^\s{}\[\],:]+/, indentationRules: { increaseIndentPattern: /({+(?=([^"]*"[^"]*")*[^"}]*$))|(\[+(?=([^"]*"[^"]*")*[^"\]]*$))/, @@ -300,7 +308,7 @@ export function activate(context: ExtensionContext) { } else if (formatEnabled && !rangeFormatting) { rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, { provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult { - let params: DocumentRangeFormattingParams = { + const params: DocumentRangeFormattingParams = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), range: client.code2ProtocolConverter.asRange(range), options: client.code2ProtocolConverter.asFormattingOptions(options) @@ -324,32 +332,33 @@ export function deactivate(): Promise { return telemetryReporter ? telemetryReporter.dispose() : Promise.resolve(null); } -function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations { - let associations: ISchemaAssociations = {}; +function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] { + const associations: ISchemaAssociation[] = []; extensions.all.forEach(extension => { - let packageJSON = extension.packageJSON; + const packageJSON = extension.packageJSON; if (packageJSON && packageJSON.contributes && packageJSON.contributes.jsonValidation) { - let jsonValidation = packageJSON.contributes.jsonValidation; + const jsonValidation = packageJSON.contributes.jsonValidation; 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 }); } }); } @@ -359,11 +368,11 @@ function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations { } function getSettings(): Settings { - let httpSettings = workspace.getConfiguration('http'); + const httpSettings = workspace.getConfiguration('http'); - let resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get('json.maxItemsComputed')))) || 5000; + const resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get('json.maxItemsComputed')))) || 5000; - let settings: Settings = { + const settings: Settings = { http: { proxy: httpSettings.get('proxy'), proxyStrictSSL: httpSettings.get('proxyStrictSSL') @@ -373,10 +382,18 @@ function getSettings(): Settings { resultLimit } }; - let schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); - let collectSchemaSettings = (schemaSettings: JSONSchemaSettings[], rootPath?: string, fileMatchPrefix?: string) => { - for (let setting of schemaSettings) { - let url = getSchemaId(setting, rootPath); + const schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); + const collectSchemaSettings = (schemaSettings: JSONSchemaSettings[], folderUri?: Uri, isMultiRoot?: boolean) => { + + let fileMatchPrefix = undefined; + if (folderUri && isMultiRoot) { + fileMatchPrefix = folderUri.toString(); + if (fileMatchPrefix[fileMatchPrefix.length - 1] === '/') { + fileMatchPrefix = fileMatchPrefix.substr(0, fileMatchPrefix.length - 1); + } + } + for (const setting of schemaSettings) { + const url = getSchemaId(setting, folderUri); if (!url) { continue; } @@ -385,69 +402,78 @@ function getSettings(): Settings { schemaSetting = schemaSettingsById[url] = { url, fileMatch: [] }; settings.json!.schemas!.push(schemaSetting); } - let fileMatches = setting.fileMatch; - let resultingFileMatches = schemaSetting.fileMatch!; + const fileMatches = setting.fileMatch; if (Array.isArray(fileMatches)) { - if (fileMatchPrefix) { - for (let fileMatch of fileMatches) { - if (fileMatch[0] === '/') { - resultingFileMatches.push(fileMatchPrefix + fileMatch); - resultingFileMatches.push(fileMatchPrefix + '/*' + fileMatch); - } else { - resultingFileMatches.push(fileMatchPrefix + '/' + fileMatch); - resultingFileMatches.push(fileMatchPrefix + '/*/' + fileMatch); - } + const resultingFileMatches = schemaSetting.fileMatch || []; + schemaSetting.fileMatch = resultingFileMatches; + const addMatch = (pattern: string) => { // filter duplicates + if (resultingFileMatches.indexOf(pattern) === -1) { + resultingFileMatches.push(pattern); + } + }; + for (const fileMatch of fileMatches) { + if (fileMatchPrefix) { + if (fileMatch[0] === '/') { + addMatch(fileMatchPrefix + fileMatch); + addMatch(fileMatchPrefix + '/*' + fileMatch); + } else { + addMatch(fileMatchPrefix + '/' + fileMatch); + addMatch(fileMatchPrefix + '/*/' + fileMatch); + } + } else { + addMatch(fileMatch); } - } else { - resultingFileMatches.push(...fileMatches); } - } - if (setting.schema) { + if (setting.schema && !schemaSetting.schema) { schemaSetting.schema = setting.schema; } } }; + const folders = workspace.workspaceFolders; + // merge global and folder settings. Qualify all file matches with the folder path. - let globalSettings = workspace.getConfiguration('json', null).get('schemas'); + const globalSettings = workspace.getConfiguration('json', null).get('schemas'); if (Array.isArray(globalSettings)) { - collectSchemaSettings(globalSettings, workspace.rootPath); + if (!folders) { + collectSchemaSettings(globalSettings); + } } - let folders = workspace.workspaceFolders; if (folders) { - for (let folder of folders) { - let folderUri = folder.uri; + const isMultiRoot = folders.length > 1; + for (const folder of folders) { + const folderUri = folder.uri; - let schemaConfigInfo = workspace.getConfiguration('json', folderUri).inspect('schemas'); + const schemaConfigInfo = workspace.getConfiguration('json', folderUri).inspect('schemas'); - let folderSchemas = schemaConfigInfo!.workspaceFolderValue; + const folderSchemas = schemaConfigInfo!.workspaceFolderValue; if (Array.isArray(folderSchemas)) { - let folderPath = folderUri.toString(); - if (folderPath[folderPath.length - 1] === '/') { - folderPath = folderPath.substr(0, folderPath.length - 1); - } - collectSchemaSettings(folderSchemas, folderUri.fsPath, folderPath); + collectSchemaSettings(folderSchemas, folderUri, isMultiRoot); } + if (Array.isArray(globalSettings)) { + collectSchemaSettings(globalSettings, folderUri, isMultiRoot); + } + } } return settings; } -function getSchemaId(schema: JSONSchemaSettings, rootPath?: string) { +function getSchemaId(schema: JSONSchemaSettings, folderUri?: Uri) { let url = schema.url; if (!url) { if (schema.schema) { url = schema.schema.id || `vscode://schemas/custom/${encodeURIComponent(hash(schema.schema).toString(16))}`; } - } else if (rootPath && (url[0] === '.' || url[0] === '/')) { - url = Uri.file(path.normalize(path.join(rootPath, url))).toString(); + } else if (folderUri && (url[0] === '.' || url[0] === '/')) { + url = folderUri.with({ path: path.posix.join(folderUri.path, url) }).toString(); } return url; } function getPackageInfo(context: ExtensionContext): IPackageInfo | undefined { - let extensionPackage = readJSONFile(context.asAbsolutePath('./package.json')); + const extensionPackage = readJSONFile(context.asAbsolutePath('./package.json')); if (extensionPackage) { return { name: extensionPackage.name, diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index dfceebce8e7..8a12ff009c3 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -100,27 +100,29 @@ }, "configurationDefaults": { "[json]": { - "editor.quickSuggestions": { - "strings": true - }, - "editor.suggest.insertMode": "replace" + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" }, "[jsonc]": { - "editor.quickSuggestions": { - "strings": true - }, - "editor.suggest.insertMode": "replace" - } + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" + } }, - "jsonValidation": [{ - "fileMatch": "*.schema.json", - "url": "http://json-schema.org/draft-07/schema#" - }] + "jsonValidation": [ + { + "fileMatch": "*.schema.json", + "url": "http://json-schema.org/draft-07/schema#" + } + ] }, "dependencies": { "request-light": "^0.2.5", "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.0.1", + "vscode-languageclient": "^6.1.1", "vscode-nls": "^4.1.1" }, "devDependencies": { 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 0c26777f01f..b66807fddf2 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-json-languageserver", "description": "JSON language server", - "version": "1.2.2", + "version": "1.2.3", "author": "Microsoft Corporation", "license": "MIT", "engines": { @@ -12,10 +12,10 @@ }, "main": "./out/jsonServerMain", "dependencies": { - "jsonc-parser": "^2.2.0", + "jsonc-parser": "^2.2.1", "request-light": "^0.2.5", - "vscode-json-languageservice": "^3.4.12", - "vscode-languageserver": "^6.0.1", + "vscode-json-languageservice": "^3.5.2", + "vscode-languageserver": "^6.1.1", "vscode-uri": "^2.1.1" }, "devDependencies": { diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index f700170b41d..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 { @@ -160,7 +165,10 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, - completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['"', ':'] } : undefined, + completionProvider: clientSnippetSupport ? { + resolveProvider: false, // turn off resolving as the current language service doesn't do anything on resolve. Also fixes #91747 + triggerCharacters: ['"', ':'] + } : undefined, hoverProvider: true, documentSymbolProvider: true, documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true, @@ -227,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. @@ -288,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] }); + }); + } } } } @@ -450,7 +462,7 @@ connection.onDocumentRangeFormatting((formatParams, token) => { const edits = languageService.format(document, formatParams.range, formatParams.options); if (edits.length > formatterMaxNumberOfEdits) { const newText = TextDocument.applyEdits(document, edits); - return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(document.getText().length - 1)), newText)]; + return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(document.getText().length)), newText)]; } return edits; } diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 9c72cea8b07..eb34e96baab 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -61,10 +61,10 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" -jsonc-parser@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" - integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== ms@2.0.0: version "2.0.0" @@ -80,14 +80,14 @@ request-light@^0.2.5: https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" -vscode-json-languageservice@^3.4.12: - version "3.4.12" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.12.tgz#e7c96a1824896a624cc7bb14f46fbf9cb7e6c5a3" - integrity sha512-+tA0KPVM1pDfORZqsQen7bY5buBpQGDTVYEobm5MoGtXNeZY2Kn0iy5wIQqXveb28LRv/I5xKE87dmNJTEaijQ== +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.0" - vscode-languageserver-textdocument "^1.0.1-next.1" - vscode-languageserver-types "^3.15.0" + jsonc-parser "^2.2.1" + vscode-languageserver-textdocument "^1.0.1" + vscode-languageserver-types "^3.15.1" vscode-nls "^4.1.1" vscode-uri "^2.1.1" @@ -96,30 +96,30 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageserver-protocol@^3.15.1: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.1.tgz#7555e595f0058b9a166f14605ad039e97fab320a" - integrity sha512-wJAo06VM9ZBnRqslplDjfz6Tdive0O7z44yNxBFA3x0/YZkXBIL6I+9rwQ/9Y//0X0eCh12FQrj+KmEXf2L5eA== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" - vscode-languageserver-types "3.15.0" + vscode-languageserver-types "3.15.1" -vscode-languageserver-textdocument@^1.0.1-next.1: - version "1.0.1-next.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1-next.1.tgz#c8f2f792c7c88d33ea8441ca04bfb8376896b671" - integrity sha512-Cmt0KsNxouns+d7/Kw/jWtWU9Z3h56z1qAA8utjDOEqrDcrTs2rDXv3EJRa99nuKM3wVf6DbWym1VqL9q71XPA== +vscode-languageserver-textdocument@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f" + integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA== -vscode-languageserver-types@3.15.0, vscode-languageserver-types@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0.tgz#c45a23308ec0967135c483b759dfaf97978d9e0a" - integrity sha512-AXteNagMhBWnZ6gNN0UB4HTiD/7TajgfHl6jaM6O7qz3zDJw0H3Jf83w05phihnBRCML+K6Ockh8f8bL0OObPw== +vscode-languageserver-types@3.15.1, vscode-languageserver-types@^3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== -vscode-languageserver@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.0.1.tgz#4f499d245f1baf83bd607dd79c4c3fd19e8cefc0" - integrity sha512-Wk4I/Dn5KNARWockdCrYuuImJz6bpYG8n2G3Kk5AU6Xy9nWNHD6YjB9/Rd99p4goViZOyETM+hYE81LnEzQZUA== +vscode-languageserver@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz#d76afc68172c27d4327ee74332b468fbc740d762" + integrity sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ== dependencies: - vscode-languageserver-protocol "^3.15.1" + vscode-languageserver-protocol "^3.15.3" vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 608f3eaed5b..cbda171084d 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -125,26 +125,26 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageclient@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.0.1.tgz#acd138e0a19a40c5788365e882ae11c164d9a460" - integrity sha512-7yZaSHichTJEyOJykI2RLQEECf9MqNLoklzC/1OVi/M8ioIsWQ1+lkN1nTsUhd6+F7p9ar9dNmPiEhL0i5uUBA== +vscode-languageclient@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.1.tgz#91b62e416c5abbf2013ae3726f314a19c22a8457" + integrity sha512-mB6d8Tg+82l8EFUfR+SBu0+lCshyKVgC5E5+MQ0/BJa+9AgeBjtG5npoGaCo4/VvWzK0ZRGm85zU5iRp1RYPIA== dependencies: semver "^6.3.0" - vscode-languageserver-protocol "^3.15.1" + vscode-languageserver-protocol "^3.15.3" -vscode-languageserver-protocol@^3.15.1: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.1.tgz#7555e595f0058b9a166f14605ad039e97fab320a" - integrity sha512-wJAo06VM9ZBnRqslplDjfz6Tdive0O7z44yNxBFA3x0/YZkXBIL6I+9rwQ/9Y//0X0eCh12FQrj+KmEXf2L5eA== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" - vscode-languageserver-types "3.15.0" + vscode-languageserver-types "3.15.1" -vscode-languageserver-types@3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0.tgz#c45a23308ec0967135c483b759dfaf97978d9e0a" - integrity sha512-AXteNagMhBWnZ6gNN0UB4HTiD/7TajgfHl6jaM6O7qz3zDJw0H3Jf83w05phihnBRCML+K6Ockh8f8bL0OObPw== +vscode-languageserver-types@3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index 26a3599cf5b..bbc5e342db0 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -85,11 +85,11 @@ "snippets": [ { "language": "markdown", - "path": "./snippets/markdown.json" + "path": "./snippets/markdown.code-snippets" } ] }, "scripts": { "update-grammar": "node ../../build/npm/update-grammar.js microsoft/vscode-markdown-tm-grammar syntaxes/markdown.tmLanguage ./syntaxes/markdown.tmLanguage.json" } -} \ No newline at end of file +} diff --git a/extensions/markdown-basics/snippets/markdown.json b/extensions/markdown-basics/snippets/markdown.code-snippets similarity index 100% rename from extensions/markdown-basics/snippets/markdown.json rename to extensions/markdown-basics/snippets/markdown.code-snippets 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/package.json b/extensions/markdown-language-features/package.json index fa3f06ca1cf..dbc8746c913 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -26,7 +26,7 @@ "onCommand:markdown.showPreviewSecuritySelector", "onCommand:markdown.api.render", "onWebviewPanel:markdown.preview", - "onWebviewEditor:vscode.markdown.preview.editor" + "onCustomEditor:vscode.markdown.preview.editor" ], "contributes": { "commands": [ @@ -309,7 +309,7 @@ "markdown.previewScripts": [ "./media/index.js" ], - "webviewEditors": [ + "customEditors": [ { "viewType": "vscode.markdown.preview.editor", "displayName": "(Experimental) VS Code Markdown Preview", 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 333a0651a44..7194bf6c85b 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.WebviewCustomEditorProvider { +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.registerWebviewCustomEditorProvider('vscode.markdown.preview.editor', this)); + this._register(vscode.window.registerCustomEditorProvider(this.customEditorViewType, this)); } public refresh() { @@ -148,12 +150,16 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this.registerDynamicPreview(preview); } - public async resolveWebviewEditor( - resource: vscode.Uri, + public async openCustomDocument(uri: vscode.Uri) { + return { uri, dispose: () => { } }; + } + + public async resolveCustomTextEditor( + document: vscode.TextDocument, webview: vscode.WebviewPanel ): Promise<void> { const preview = DynamicMarkdownPreview.revive( - { resource, locked: false, resourceColumn: vscode.ViewColumn.One }, + { resource: document.uri, locked: false, resourceColumn: vscode.ViewColumn.One }, webview, this._contentProvider, this._previewConfigurations, diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 33cb220b449..c500b692eaa 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -10,7 +10,7 @@ import * as vscode from 'vscode'; import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions'; import { Slugifier } from './slugify'; import { SkinnyTextDocument } from './tableOfContentsProvider'; -import { Schemes, isOfScheme } from './util/links'; +import { MarkdownFileExtensions, Schemes, isOfScheme } from './util/links'; const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g; @@ -251,7 +251,9 @@ export class MarkdownEngine { } } - if (uri.fragment) { + const extname = path.extname(uri.fsPath); + + if (uri.fragment && (extname === '' || MarkdownFileExtensions.includes(extname))) { uri = uri.with({ fragment: this.slugifier.fromHeading(uri.fragment).value }); 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/src/util/links.ts b/extensions/markdown-language-features/src/util/links.ts index 74383466f72..58725d6f778 100644 --- a/extensions/markdown-language-features/src/util/links.ts +++ b/extensions/markdown-language-features/src/util/links.ts @@ -32,3 +32,15 @@ export function getUriForLinkWithKnownExternalScheme(link: string): vscode.Uri | export function isOfScheme(scheme: string, link: string): boolean { return link.toLowerCase().startsWith(scheme); } + +export const MarkdownFileExtensions: readonly string[] = [ + '.md', + '.mkd', + '.mdwn', + '.mdown', + '.markdown', + '.markdn', + '.mdtxt', + '.mdtext', + '.workbook', +]; diff --git a/extensions/markdown-language-features/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/package.nls.json b/extensions/merge-conflict/package.nls.json index 3310dac7e21..3353f40b316 100644 --- a/extensions/merge-conflict/package.nls.json +++ b/extensions/merge-conflict/package.nls.json @@ -14,10 +14,10 @@ "command.compare": "Compare Current Conflict", "config.title": "Merge Conflict", "config.autoNavigateNextConflictEnabled": "Whether to automatically navigate to the next merge conflict after resolving a merge conflict.", - "config.codeLensEnabled": "Create a Code Lens for merge conflict blocks within editor.", + "config.codeLensEnabled": "Create a CodeLens for merge conflict blocks within editor.", "config.decoratorsEnabled": "Create decorators for merge conflict blocks within editor.", "config.diffViewPosition": "Controls where the diff view should be opened when comparing changes in merge conflicts.", "config.diffViewPosition.current": "Open the diff view in the current editor group.", "config.diffViewPosition.beside": "Open the diff view next to the current editor group.", "config.diffViewPosition.below": "Open the diff view below the current editor group." -} \ No newline at end of file +} 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 cbd9645341a..265dffcacf7 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -18,10 +18,10 @@ "watch": "gulp watch-extension:npm" }, "dependencies": { - "jsonc-parser": "^2.1.1", + "jsonc-parser": "^2.2.1", "minimatch": "^3.0.4", "request-light": "^0.2.5", - "vscode-nls": "^4.0.0" + "vscode-nls": "^4.1.1" }, "devDependencies": { "@types/minimatch": "^3.0.3", @@ -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/main.ts b/extensions/npm/src/main.ts index 4092e27e662..b79638ed1f0 100644 --- a/extensions/npm/src/main.ts +++ b/extensions/npm/src/main.ts @@ -21,7 +21,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void> configureHttpRequest(); let d = vscode.workspace.onDidChangeConfiguration((e) => { configureHttpRequest(); - if (e.affectsConfiguration('npm.exclude')) { + if (e.affectsConfiguration('npm.exclude') || e.affectsConfiguration('npm.autoDetect')) { invalidateTasksCache(); if (treeDataProvider) { treeDataProvider.refresh(); diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index b5234c5fc1b..0a4908ec440 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -12,7 +12,7 @@ import { import { visit, JSONVisitor } from 'jsonc-parser'; import { NpmTaskDefinition, getPackageJsonUriFromTask, getScripts, - isWorkspaceFolder, getTaskName, createTask, extractDebugArgFromScript, startDebugging + isWorkspaceFolder, getTaskName, createTask, extractDebugArgFromScript, startDebugging, isAutoDetectionEnabled } from './tasks'; import * as nls from 'vscode-nls'; @@ -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; @@ -119,8 +113,8 @@ class NpmScript extends TreeItem { } class NoScripts extends TreeItem { - constructor() { - super(localize('noScripts', 'No scripts found'), TreeItemCollapsibleState.None); + constructor(message: string) { + super(message, TreeItemCollapsibleState.None); this.contextValue = 'noscripts'; } } @@ -231,7 +225,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider<TreeItem> { public refresh() { this.taskTree = null; - this._onDidChangeTreeData.fire(); + this._onDidChangeTreeData.fire(null); } getTreeItem(element: TreeItem): TreeItem { @@ -260,7 +254,11 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider<TreeItem> { if (taskItems) { this.taskTree = this.buildTaskTree(taskItems); if (this.taskTree.length === 0) { - this.taskTree = [new NoScripts()]; + let message = localize('noScripts', 'No scripts found.'); + if (!isAutoDetectionEnabled()) { + message = localize('autoDetectIsOff', 'The setting "npm.autoDetect" is "off".'); + } + this.taskTree = [new NoScripts(message)]; } } } diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index b9cedcc9a33..661796a3756 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -190,8 +190,8 @@ export async function provideNpmScripts(): Promise<Task[]> { return cachedTasks; } -function isAutoDetectionEnabled(folder: WorkspaceFolder): boolean { - return workspace.getConfiguration('npm', folder.uri).get<AutoDetect>('autoDetect') === 'on'; +export function isAutoDetectionEnabled(folder?: WorkspaceFolder): boolean { + return workspace.getConfiguration('npm', folder?.uri).get<AutoDetect>('autoDetect') === 'on'; } function isExcluded(folder: WorkspaceFolder, packageJsonUri: Uri) { diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index 4c6c723c3cb..9b8a23c9264 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -79,10 +79,10 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" -jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== minimatch@^3.0.4: version "3.0.4" @@ -105,11 +105,6 @@ request-light@^0.2.5: https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== - vscode-nls@^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/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/package.json b/extensions/package.json index df820979f1a..7b4e8171c1f 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^3.8.1-rc" + "typescript": "3.8.3" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/perl/cgmanifest.json b/extensions/perl/cgmanifest.json index 83d91107671..b7c850dd1aa 100644 --- a/extensions/perl/cgmanifest.json +++ b/extensions/perl/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "textmate/perl.tmbundle", "repositoryUrl": "https://github.com/textmate/perl.tmbundle", - "commitHash": "80826abe75250286c2a1a07958e50e8551d3f50c" + "commitHash": "d9841a0878239fa43f88c640f8d458590f97e8f5" } }, "licenseDetail": [ 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/package.json b/extensions/php/package.json index 2d0e4204ec1..0bd740a10ad 100644 --- a/extensions/php/package.json +++ b/extensions/php/package.json @@ -54,7 +54,7 @@ "snippets": [ { "language": "php", - "path": "./snippets/php.snippets.json" + "path": "./snippets/php.code-snippets" } ] }, diff --git a/extensions/php/snippets/php.snippets.json b/extensions/php/snippets/php.code-snippets similarity index 99% rename from extensions/php/snippets/php.snippets.json rename to extensions/php/snippets/php.code-snippets index a0705e3aed4..b2e9078d91f 100644 --- a/extensions/php/snippets/php.snippets.json +++ b/extensions/php/snippets/php.code-snippets @@ -260,4 +260,4 @@ ], "description": "Try catch block" } -} \ No newline at end of file +} 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/powershell/package.json b/extensions/powershell/package.json index 232acb1a7d2..fb45b704b85 100644 --- a/extensions/powershell/package.json +++ b/extensions/powershell/package.json @@ -21,7 +21,7 @@ }], "snippets": [{ "language": "powershell", - "path": "./snippets/powershell.json" + "path": "./snippets/powershell.code-snippets" }] }, "scripts": { diff --git a/extensions/powershell/snippets/powershell.json b/extensions/powershell/snippets/powershell.code-snippets similarity index 100% rename from extensions/powershell/snippets/powershell.json rename to extensions/powershell/snippets/powershell.code-snippets diff --git a/extensions/python/cgmanifest.json b/extensions/python/cgmanifest.json index 3ee82895a82..97a9d1d1118 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": "0b09c1fca238d22e15ac5712d03f9bf6da626f9c" } }, "license": "MIT", diff --git a/extensions/python/syntaxes/MagicPython.tmLanguage.json b/extensions/python/syntaxes/MagicPython.tmLanguage.json index f59618f75ff..a6920a06305 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/0b09c1fca238d22e15ac5712d03f9bf6da626f9c", "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/shellscript/package.json b/extensions/shellscript/package.json index 211401a070c..207ae27aa1b 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -12,10 +12,14 @@ "contributes": { "languages": [{ "id": "shellscript", - "aliases": ["Shell Script", "shellscript", "bash", "sh", "zsh", "ksh"], - "extensions": [".sh", ".bash", ".bashrc", ".bash_aliases", ".bash_profile", ".bash_login", ".ebuild", ".install", ".profile", ".bash_logout", ".zsh", ".zshrc", ".zprofile", ".zlogin", ".zlogout", ".zshenv", ".zsh-theme", ".ksh"], - "filenames": ["APKBUILD", "PKGBUILD"], - "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh|ksh|ash|qsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", + "aliases": ["Shell Script", "shellscript", "bash", "sh", "zsh", "ksh", "csh"], + "extensions": [".sh", ".bash", ".bashrc", ".bash_aliases", ".bash_profile", ".bash_login", ".ebuild", ".install", ".profile", ".bash_logout", ".zsh", ".zshrc", ".zprofile", ".zlogin", ".zlogout", ".zshenv", ".zsh-theme", ".ksh", ".csh", ".cshrc"], + "filenames": [ + "APKBUILD", + "PKGBUILD", + ".envrc" + ], + "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh|ksh|ash|qsh|csh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", "configuration": "./language-configuration.json", "mimetypes": ["text/x-shellscript"] }], diff --git a/extensions/sql/cgmanifest.json b/extensions/sql/cgmanifest.json index 7abcea36e5d..859d6782240 100644 --- a/extensions/sql/cgmanifest.json +++ b/extensions/sql/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "Microsoft/vscode-mssql", "repositoryUrl": "https://github.com/Microsoft/vscode-mssql", - "commitHash": "a79741f76fd33bd137a8c28172c9750b978309b6" + "commitHash": "a542fe96780e6b274adb281810d419a512fb5bb4" } }, "license": "MIT", - "version": "1.6.0" + "version": "1.9.0" } ], "version": 1 diff --git a/extensions/sql/syntaxes/sql.tmLanguage.json b/extensions/sql/syntaxes/sql.tmLanguage.json index 1dd6903783f..d4173da5b23 100644 --- a/extensions/sql/syntaxes/sql.tmLanguage.json +++ b/extensions/sql/syntaxes/sql.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/vscode-mssql/commit/a79741f76fd33bd137a8c28172c9750b978309b6", + "version": "https://github.com/Microsoft/vscode-mssql/commit/a542fe96780e6b274adb281810d419a512fb5bb4", "name": "SQL", "scopeName": "source.sql", "patterns": [ @@ -404,7 +404,7 @@ } }, "comment": "this is faster than the next begin/end rule since sub-pattern will match till end-of-line and SQL files tend to have very long lines.", - "match": "(N)?(')[^']*(')", + "match": "(N)?(')(?:[^'\\\\]|\\\\.)*(')", "name": "string.quoted.single.sql" }, { @@ -437,7 +437,7 @@ } }, "comment": "this is faster than the next begin/end rule since sub-pattern will match till end-of-line and SQL files tend to have very long lines.", - "match": "(`)[^`\\\\]*(`)", + "match": "(`)(?:[^`\\\\]|\\\\.)*(`)", "name": "string.quoted.other.backtick.sql" }, { @@ -470,7 +470,7 @@ } }, "comment": "this is faster than the next begin/end rule since sub-pattern will match till end-of-line and SQL files tend to have very long lines.", - "match": "(\")[^\"#]*(\")", + "match": "(\")(?:[^\"#\\\\]|\\\\.)*(\")", "name": "string.quoted.double.sql" }, { diff --git a/extensions/swift/package.json b/extensions/swift/package.json index f88ac07ea5e..a8f75268efc 100644 --- a/extensions/swift/package.json +++ b/extensions/swift/package.json @@ -23,7 +23,7 @@ }], "snippets": [{ "language": "swift", - "path": "./snippets/swift.json" + "path": "./snippets/swift.code-snippets" }] } } diff --git a/extensions/swift/snippets/swift.json b/extensions/swift/snippets/swift.code-snippets similarity index 100% rename from extensions/swift/snippets/swift.json rename to extensions/swift/snippets/swift.code-snippets 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..dcb4acc6b1f 100644 --- a/extensions/typescript-basics/package.json +++ b/extensions/typescript-basics/package.json @@ -79,14 +79,42 @@ } } ], - "snippets": [ + "semanticTokenScopes": [ { "language": "typescript", - "path": "./snippets/typescript.json" + "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", - "path": "./snippets/typescript.json" + "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", + "path": "./snippets/typescript.code-snippets" + }, + { + "language": "typescriptreact", + "path": "./snippets/typescript.code-snippets" } ] } diff --git a/extensions/typescript-basics/snippets/typescript.json b/extensions/typescript-basics/snippets/typescript.code-snippets similarity index 100% rename from extensions/typescript-basics/snippets/typescript.json rename to extensions/typescript-basics/snippets/typescript.code-snippets 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 c52278cc655..20513acc2d5 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -16,12 +16,12 @@ "Programming Languages" ], "dependencies": { - "jsonc-parser": "^2.1.1", + "jsonc-parser": "^2.2.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.6.6", + "typescript-vscode-sh-plugin": "^0.6.13", "vscode-extension-telemetry": "0.1.1", - "vscode-nls": "^4.0.0" + "vscode-nls": "^4.1.1" }, "devDependencies": { "@types/node": "^12.11.7", @@ -638,6 +638,42 @@ "description": "%typescript.preferences.importModuleSpecifier%", "scope": "resource" }, + "javascript.preferences.importModuleSpecifierEnding": { + "type": "string", + "enum": [ + "auto", + "minimal", + "index", + "js" + ], + "markdownEnumDescriptions": [ + "%typescript.preferences.importModuleSpecifierEnding.auto%", + "%typescript.preferences.importModuleSpecifierEnding.minimal%", + "%typescript.preferences.importModuleSpecifierEnding.index%", + "%typescript.preferences.importModuleSpecifierEnding.js%" + ], + "default": "auto", + "description": "%typescript.preferences.importModuleSpecifierEnding%", + "scope": "resource" + }, + "typescript.preferences.importModuleSpecifierEnding": { + "type": "string", + "enum": [ + "auto", + "minimal", + "index", + "js" + ], + "markdownEnumDescriptions": [ + "%typescript.preferences.importModuleSpecifierEnding.auto%", + "%typescript.preferences.importModuleSpecifierEnding.minimal%", + "%typescript.preferences.importModuleSpecifierEnding.index%", + "%typescript.preferences.importModuleSpecifierEnding.js%" + ], + "default": "auto", + "description": "%typescript.preferences.importModuleSpecifierEnding%", + "scope": "resource" + }, "javascript.preferences.renameShorthandProperties": { "type": "boolean", "default": true, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index a421936acb9..a2bf1f3eeed 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -70,6 +70,11 @@ "typescript.preferences.importModuleSpecifier.auto": "Automatically select import path style. Prefers using a relative import if `baseUrl` is configured and the relative path has fewer segments than the non-relative import.", "typescript.preferences.importModuleSpecifier.relative": "Relative to the file location.", "typescript.preferences.importModuleSpecifier.nonRelative": "Based on the `baseUrl` configured in your `jsconfig.json` / `tsconfig.json`.", + "typescript.preferences.importModuleSpecifierEnding": "Preferred path ending for auto imports.", + "typescript.preferences.importModuleSpecifierEnding.auto": "Use project settings to select a default.", + "typescript.preferences.importModuleSpecifierEnding.minimal": "Shorten `./component/index.js` to `./component`.", + "typescript.preferences.importModuleSpecifierEnding.index": "Shorten `./component/index.js` to `./component/index`", + "typescript.preferences.importModuleSpecifierEnding.js": "Do not shorten path endings; include the `.js` extension.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code. Requires using TypeScript 2.9 or newer in the workspace.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", 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 52e82adf2a8..594687ab97f 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -83,13 +83,21 @@ class MyCompletionItem extends vscode.CompletionItem { if (completionContext.isMemberCompletion && completionContext.dotAccessorContext) { this.filterText = completionContext.dotAccessorContext.text + (this.insertText || this.label); if (!this.range) { - this.range = completionContext.dotAccessorContext.range; + const replacementRange = this.getReplaceRange(line); + if (replacementRange) { + this.range = { + inserting: completionContext.dotAccessorContext.range, + replacing: completionContext.dotAccessorContext.range.union(replacementRange), + }; + } else { + this.range = completionContext.dotAccessorContext.range; + } this.insertText = this.filterText; } } if (tsEntry.kindModifiers) { - const kindModifiers = new Set(tsEntry.kindModifiers.split(/\s+/g)); + const kindModifiers = new Set(tsEntry.kindModifiers.split(/,|\s+/g)); if (kindModifiers.has(PConst.KindModifiers.optional)) { if (!this.insertText) { this.insertText = this.label; @@ -135,7 +143,6 @@ class MyCompletionItem extends vscode.CompletionItem { } else { return wordStart === '#' ? undefined : this.tsEntry.name.replace(/^#/, ''); } - return undefined; } // For `this.` completions, generally don't set the filter text since we don't want them to be overly prioritized. #74164 @@ -162,6 +169,16 @@ class MyCompletionItem extends vscode.CompletionItem { return; } + const replaceRange = this.getReplaceRange(line); + if (replaceRange) { + this.range = { + inserting: new vscode.Range(replaceRange.start, this.position), + replacing: replaceRange + }; + } + } + + private getReplaceRange(line: string) { const wordRange = this.document.getWordRangeAtPosition(this.position); let replaceRange = wordRange; @@ -177,12 +194,7 @@ class MyCompletionItem extends vscode.CompletionItem { } } - if (replaceRange) { - this.range = { - inserting: new vscode.Range(replaceRange.start, this.position), - replacing: replaceRange - }; - } + return replaceRange; } private static convertKind(kind: string): vscode.CompletionItemKind { @@ -204,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: @@ -260,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/fileConfigurationManager.ts b/extensions/typescript-language-features/src/features/fileConfigurationManager.ts index cad4e5196b9..be7431eac9f 100644 --- a/extensions/typescript-language-features/src/features/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/features/fileConfigurationManager.ts @@ -7,9 +7,10 @@ import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; +import { Disposable } from '../utils/dispose'; +import * as fileSchemes from '../utils/fileSchemes'; import { isTypeScriptDocument } from '../utils/languageModeIds'; import { ResourceMap } from '../utils/resourceMap'; -import { Disposable } from '../utils/dispose'; function objsAreEqual<T>(a: T, b: T): boolean { @@ -144,9 +145,7 @@ export default class FileConfigurationManager extends Disposable { isTypeScriptDocument(document) ? 'typescript.format' : 'javascript.format', document.uri); - // `semicolons` added to `Proto.FormatCodeSettings` in TypeScript 3.7: - // remove intersection type after upgrading TypeScript. - const settings: Proto.FormatCodeSettings & { semicolons?: string } = { + return { tabSize: options.tabSize, indentSize: options.tabSize, convertTabsToSpaces: options.insertSpaces, @@ -169,8 +168,6 @@ export default class FileConfigurationManager extends Disposable { placeOpenBraceOnNewLineForControlBlocks: config.get<boolean>('placeOpenBraceOnNewLineForControlBlocks'), semicolons: config.get<Proto.SemicolonPreference>('semicolons'), }; - - return settings; } private getPreferences(document: vscode.TextDocument): Proto.UserPreferences { @@ -182,13 +179,18 @@ export default class FileConfigurationManager extends Disposable { isTypeScriptDocument(document) ? 'typescript.preferences' : 'javascript.preferences', document.uri); - return { + // `importModuleSpecifierEnding` added to `Proto.UserPreferences` in TypeScript 3.9: + // remove intersection type after upgrading TypeScript. + const preferences: Proto.UserPreferences & { importModuleSpecifierEnding?: string } = { quotePreference: this.getQuoteStylePreference(config), importModuleSpecifierPreference: getImportModuleSpecifierPreference(config), - allowTextChangesInNewFiles: document.uri.scheme === 'file', + importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(config), + allowTextChangesInNewFiles: document.uri.scheme === fileSchemes.file, providePrefixAndSuffixTextForRename: config.get<boolean>('renameShorthandProperties', true), allowRenameOfImportPath: true, }; + + return preferences; } private getQuoteStylePreference(config: vscode.WorkspaceConfiguration) { @@ -207,3 +209,12 @@ function getImportModuleSpecifierPreference(config: vscode.WorkspaceConfiguratio default: return undefined; } } + +function getImportModuleSpecifierEndingPreference(config: vscode.WorkspaceConfiguration) { + switch (config.get<string>('importModuleSpecifierEnding')) { + case 'minimal': return 'minimal'; + case 'index': return 'index'; + case 'js': return 'js'; + default: return 'auto'; + } +} 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/quickFix.ts b/extensions/typescript-language-features/src/features/quickFix.ts index 5edaaa6b5df..0023c41600e 100644 --- a/extensions/typescript-language-features/src/features/quickFix.ts +++ b/extensions/typescript-language-features/src/features/quickFix.ts @@ -126,19 +126,29 @@ class DiagnosticsSet { } } -class CodeActionSet { - private readonly _actions = new Set<vscode.CodeAction>(); - private readonly _fixAllActions = new Map<{}, vscode.CodeAction>(); +class VsCodeCodeAction extends vscode.CodeAction { + constructor( + public readonly tsAction: Proto.CodeFixAction, + title: string, + kind: vscode.CodeActionKind, + ) { + super(title, kind); + } +} - public get values(): Iterable<vscode.CodeAction> { +class CodeActionSet { + private readonly _actions = new Set<VsCodeCodeAction>(); + private readonly _fixAllActions = new Map<{}, VsCodeCodeAction>(); + + public get values(): Iterable<VsCodeCodeAction> { return this._actions; } - public addAction(action: vscode.CodeAction) { + public addAction(action: VsCodeCodeAction) { this._actions.add(action); } - public addFixAllAction(fixId: {}, action: vscode.CodeAction) { + public addFixAllAction(fixId: {}, action: VsCodeCodeAction) { const existing = this._fixAllActions.get(fixId); if (existing) { // reinsert action at back of actions list @@ -219,7 +229,13 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { for (const diagnostic of fixableDiagnostics.values) { await this.getFixesForDiagnostic(document, file, diagnostic, results, token); } - return Array.from(results.values); + + const allActions = Array.from(results.values); + const allTsActions = allActions.map(x => x.tsAction); + for (const action of allActions) { + action.isPreferred = isPreferredFix(action.tsAction, allTsActions); + } + return allActions; } private async getFixesForDiagnostic( @@ -259,8 +275,8 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { private getSingleFixForTsCodeAction( diagnostic: vscode.Diagnostic, tsAction: Proto.CodeFixAction - ): vscode.CodeAction { - const codeAction = new vscode.CodeAction(tsAction.description, vscode.CodeActionKind.QuickFix); + ): VsCodeCodeAction { + const codeAction = new VsCodeCodeAction(tsAction, tsAction.description, vscode.CodeActionKind.QuickFix); codeAction.edit = getEditForCodeAction(this.client, tsAction); codeAction.diagnostics = [diagnostic]; codeAction.command = { @@ -268,7 +284,6 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { arguments: [tsAction], title: '' }; - codeAction.isPreferred = isPreferredFix(tsAction); return codeAction; } @@ -294,7 +309,8 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { return results; } - const action = new vscode.CodeAction( + const action = new VsCodeCodeAction( + tsAction, tsAction.fixAllDescription || localize('fixAllInFileLabel', '{0} (Fix all in file)', tsAction.description), vscode.CodeActionKind.QuickFix); action.diagnostics = [diagnostic]; @@ -317,20 +333,37 @@ const fixAllErrorCodes = new Map<number, number>([ ]); -const preferredFixes = new Set([ - 'annotateWithTypeFromJSDoc', - 'constructorForDerivedNeedSuperCall', - 'extendsInterfaceBecomesImplements', - 'fixAwaitInSyncFunction', - 'fixClassIncorrectlyImplementsInterface', - 'fixUnreachableCode', - 'forgottenThisPropertyAccess', - 'spelling', - 'unusedIdentifier', - 'addMissingAwait', +const preferredFixes = new Map<string, /* priorty */number>([ + ['annotateWithTypeFromJSDoc', 0], + ['constructorForDerivedNeedSuperCall', 0], + ['extendsInterfaceBecomesImplements', 0], + ['fixAwaitInSyncFunction', 0], + ['fixClassIncorrectlyImplementsInterface', 1], + ['fixUnreachableCode', 0], + ['unusedIdentifier', 0], + ['forgottenThisPropertyAccess', 0], + ['spelling', 1], + ['addMissingAwait', 0], ]); -function isPreferredFix(tsAction: Proto.CodeFixAction): boolean { - return preferredFixes.has(tsAction.fixName); + +function isPreferredFix( + tsAction: Proto.CodeFixAction, + allActions: readonly Proto.CodeFixAction[] +): boolean { + const priority = preferredFixes.get(tsAction.fixName); + if (typeof priority === 'undefined') { + return false; + } + return allActions.every(otherAction => { + if (otherAction === tsAction) { + return true; + } + const otherPriority = preferredFixes.get(otherAction.fixName); + if (typeof otherPriority === 'undefined') { + return true; + } + return priority >= otherPriority; + }); } export function register( diff --git a/extensions/typescript-language-features/src/features/refactor.ts b/extensions/typescript-language-features/src/features/refactor.ts index e863cf03bd9..59e5837600e 100644 --- a/extensions/typescript-language-features/src/features/refactor.ts +++ b/extensions/typescript-language-features/src/features/refactor.ts @@ -255,7 +255,6 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { return this.appendInvalidActions(actions); } - private convertApplicableRefactors( body: Proto.ApplicableRefactorInfo[], document: vscode.TextDocument, @@ -273,7 +272,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { actions.push(codeAction); } else { for (const action of info.actions) { - actions.push(this.refactorActionToCodeAction(action, document, info, rangeOrSelection)); + actions.push(this.refactorActionToCodeAction(action, document, info, rangeOrSelection, info.actions)); } } } @@ -284,7 +283,8 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { action: Proto.RefactorActionInfo, document: vscode.TextDocument, info: Proto.ApplicableRefactorInfo, - rangeOrSelection: vscode.Range | vscode.Selection + rangeOrSelection: vscode.Range | vscode.Selection, + allActions: readonly Proto.RefactorActionInfo[], ) { const codeAction = new vscode.CodeAction(action.description, TypeScriptRefactorProvider.getKind(action)); codeAction.command = { @@ -292,7 +292,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { command: ApplyRefactoringCommand.ID, arguments: [document, info.name, action.name, rangeOrSelection], }; - codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action); + codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions); return codeAction; } @@ -310,10 +310,26 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { } private static isPreferred( - action: Proto.RefactorActionInfo + action: Proto.RefactorActionInfo, + allActions: readonly Proto.RefactorActionInfo[], ): boolean { if (Extract_Constant.matches(action)) { - return action.name.endsWith('scope_0'); + // Only mark the action with the lowest scope as preferred + const getScope = (name: string) => { + const scope = name.match(/scope_(\d)/)?.[1]; + return scope ? +scope : undefined; + }; + const scope = getScope(action.name); + if (typeof scope !== 'number') { + return false; + } + + return allActions + .filter(otherAtion => otherAtion !== action && Extract_Constant.matches(otherAtion)) + .every(otherAction => { + const otherScope = getScope(otherAction.name); + return typeof otherScope === 'number' ? scope < otherScope : true; + }); } if (Extract_Type.matches(action) || Extract_Interface.matches(action)) { return true; diff --git a/extensions/typescript-language-features/src/features/referencesCodeLens.ts b/extensions/typescript-language-features/src/features/referencesCodeLens.ts index 5e92ce06e01..0cf8d3f0a51 100644 --- a/extensions/typescript-language-features/src/features/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/features/referencesCodeLens.ts @@ -94,11 +94,19 @@ 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: case PConst.Kind.memberVariable: + // Don't show if child and parent have same start + // For https://github.com/microsoft/vscode/issues/90396 + if (parent && + typeConverters.Position.fromLocation(parent.spans[0].start).isEqual(typeConverters.Position.fromLocation(item.spans[0].start)) + ) { + return null; + } + // Only show if parent is a class type object (not a literal) switch (parent?.kind) { case PConst.Kind.class: 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 252d946eb25..92b6e479330 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -14,11 +14,14 @@ import { TokenType, TokenModifier, TokenEncodingConsts, VersionRequirement } fro const minTypeScriptVersion = API.fromVersionString(`${VersionRequirement.major}.${VersionRequirement.minor}`); +// as we don't do deltas, for performance reasons, don't compute semantic tokens for documents above that limit +const CONTENT_LENGTH_LIMIT = 100000; + export function register(selector: vscode.DocumentSelector, client: ITypeScriptServiceClient) { return new VersionDependentRegistration(client, minTypeScriptVersion, () => { const provider = new DocumentSemanticTokensProvider(client); return vscode.Disposable.from( - vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, provider.getLegend()), + // register only as a range provider vscode.languages.registerDocumentRangeSemanticTokensProvider(selector, provider, provider.getLegend()), ); }); @@ -35,18 +38,12 @@ class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensPro } getLegend(): vscode.SemanticTokensLegend { - if (tokenTypes.length !== TokenType._) { - console.warn('typescript-vscode-sh-plugin has added new tokens types.'); - } - if (tokenModifiers.length !== TokenModifier._) { - console.warn('typescript-vscode-sh-plugin has added new tokens modifiers.'); - } return new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); } 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); @@ -54,9 +51,10 @@ 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.offsetAt(range.end) - document.offsetAt(range.start) > CONTENT_LENGTH_LIMIT)) { return null; } + const start = document.offsetAt(range.start); const length = document.offsetAt(range.end) - start; return this._provideSemanticTokens(document, { file, start, length }, token); @@ -68,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) { @@ -84,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'); } @@ -119,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 @@ -146,6 +162,7 @@ tokenTypes[TokenType.typeParameter] = 'typeParameter'; tokenTypes[TokenType.type] = 'type'; tokenTypes[TokenType.parameter] = 'parameter'; tokenTypes[TokenType.variable] = 'variable'; +tokenTypes[TokenType.enumMember] = 'enumMember'; tokenTypes[TokenType.property] = 'property'; tokenTypes[TokenType.function] = 'function'; tokenTypes[TokenType.member] = 'member'; @@ -155,6 +172,16 @@ tokenModifiers[TokenModifier.async] = 'async'; 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._) { + console.warn('typescript-vscode-sh-plugin has added new tokens types.'); +} +if (tokenModifiers.filter(t => !!t).length !== TokenModifier._) { + console.warn('typescript-vscode-sh-plugin has added new tokens modifiers.'); +} // mapping for the original ExperimentalProtocol.ClassificationType from TypeScript (only used when plugin is not available) const tokenTypeMap: number[] = []; diff --git a/extensions/typescript-language-features/src/features/signatureHelp.ts b/extensions/typescript-language-features/src/features/signatureHelp.ts index 0f79a2aef8e..8e751f8c114 100644 --- a/extensions/typescript-language-features/src/features/signatureHelp.ts +++ b/extensions/typescript-language-features/src/features/signatureHelp.ts @@ -40,14 +40,27 @@ class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider { const info = response.body; const result = new vscode.SignatureHelp(); - result.activeSignature = info.selectedItemIndex; - result.activeParameter = this.getActiveParmeter(info); result.signatures = info.items.map(signature => this.convertSignature(signature)); + result.activeSignature = this.getActiveSignature(context, info, result.signatures); + result.activeParameter = this.getActiveParameter(info); return result; } - private getActiveParmeter(info: Proto.SignatureHelpItems): number { + private getActiveSignature(context: vscode.SignatureHelpContext, info: Proto.SignatureHelpItems, signatures: readonly vscode.SignatureInformation[]): number { + // Try matching the previous active signature's label to keep it selected + const previouslyActiveSignature = context.activeSignatureHelp?.signatures[context.activeSignatureHelp.activeSignature]; + if (previouslyActiveSignature && context.isRetrigger) { + const existingIndex = signatures.findIndex(other => other.label === previouslyActiveSignature?.label); + if (existingIndex >= 0) { + return existingIndex; + } + } + + return info.selectedItemIndex; + } + + private getActiveParameter(info: Proto.SignatureHelpItems): number { const activeSignature = info.items[info.selectedItemIndex]; if (activeSignature && activeSignature.isVariadic) { return Math.min(info.argumentIndex, activeSignature.parameters.length - 1); 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/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 5652967abfc..6e926eb8d7e 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -1,11 +1,2 @@ import * as Proto from 'typescript/lib/protocol'; export = Proto; - -declare module "typescript/lib/protocol" { - // TODO: Remove this hardcoded type once we update to TS 3.8+ that brings in the proper types - interface Response { - performanceData?: { - updateGraphDurationMs?: number; - } - } -} diff --git a/extensions/typescript-language-features/src/test/completions.test.ts b/extensions/typescript-language-features/src/test/completions.test.ts index 20afd43678e..0c928fb4a39 100644 --- a/extensions/typescript-language-features/src/test/completions.test.ts +++ b/extensions/typescript-language-features/src/test/completions.test.ts @@ -622,4 +622,29 @@ suite('TypeScript Completions', () => { `Config: ${config}`); }); }); + + test('Replace should work after this. (#91105)', async () => { + await updateConfig(testDocumentUri, { [Config.insertMode]: 'replace' }); + + const editor = await createTestEditor(testDocumentUri, + `class A {`, + ` abc = 1`, + ` foo() {`, + ` this.$0abc`, + ` }`, + `}`, + ); + + await acceptFirstSuggestion(testDocumentUri, _disposables); + + assertEditorContents(editor, + joinLines( + `class A {`, + ` abc = 1`, + ` foo() {`, + ` this.abc`, + ` }`, + `}`, + )); + }); }); diff --git a/extensions/typescript-language-features/src/test/referencesCodeLens.test.ts b/extensions/typescript-language-features/src/test/referencesCodeLens.test.ts index 7ca35ed89cc..89689d57ae6 100644 --- a/extensions/typescript-language-features/src/test/referencesCodeLens.test.ts +++ b/extensions/typescript-language-features/src/test/referencesCodeLens.test.ts @@ -91,6 +91,19 @@ suite('TypeScript References', () => { const codeLenses = await getCodeLenses(testDocumentUri); assert.strictEqual(codeLenses?.length, 0); }); + + test('Should not show duplicate references on ES5 class (https://github.com/microsoft/vscode/issues/90396)', async () => { + const testDocumentUri = vscode.Uri.parse('untitled:test3.js'); + await createTestEditor(testDocumentUri, + `function A() {`, + ` console.log("hi");`, + `}`, + `A.x = {};`, + ); + + const codeLenses = await getCodeLenses(testDocumentUri); + assert.strictEqual(codeLenses?.length, 1); + }); }); function getCodeLenses(document: vscode.Uri): Thenable<readonly vscode.CodeLens[] | undefined> { diff --git a/extensions/typescript-language-features/src/tsServer/serverError.ts b/extensions/typescript-language-features/src/tsServer/serverError.ts index 99d13d8181e..6f4c8f6bf35 100644 --- a/extensions/typescript-language-features/src/tsServer/serverError.ts +++ b/extensions/typescript-language-features/src/tsServer/serverError.ts @@ -20,7 +20,7 @@ export class TypeScriptServerError extends Error { private constructor( serverId: string, - version: TypeScriptVersion, + public readonly version: TypeScriptVersion, private readonly response: Proto.Response, public readonly serverMessage: string | undefined, public readonly serverStack: string | undefined 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 1fb1dce18b0..2b5ba716d21 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,16 +99,18 @@ 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(); public readonly telemetryReporter: TelemetryReporter; @@ -138,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(); }); @@ -306,27 +309,27 @@ export default class TypeScriptServiceClient extends Disposable implements IType private token: number = 0; private startService(resendModels: boolean = false): ServerState.State { - if (this.isDisposed) { + if (this.isDisposed || this.hasServerFatallyCrashedTooManyTimes) { 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__ @@ -340,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) => { @@ -402,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(apiVersion); if (apiVersion.gte(API.v300)) { this.loadingIndicator.startedLoadingProject(undefined /* projectName */); @@ -413,17 +416,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.serverState; } - public onVersionStatusClicked(): Thenable<void> { - return this.showVersionPicker(false); - } - - private showVersionPicker(firstRun: boolean): Thenable<void> { - return this.versionPicker.show(firstRun).then(change => { - if (firstRun || !change.newVersion || !change.oldVersion || change.oldVersion.path === change.newVersion.path) { - return; - } - this.restartTsServer(); - }); + public async showVersionPicker(): Promise<void> { + this._versionManager.promptUserForVersion(); } public async openTsServerLogFile(): Promise<boolean> { @@ -478,7 +472,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.bufferSyncSupport.reset(); const watchOptions = this.apiVersion.gte(API.v380) - ? vscode.workspace.getConfiguration('typescript').get<Proto.WatchOptions | undefined>('tsserver.watchOptions') + ? this.configuration.watchOptions : undefined; const configureOptions: Proto.ConfigureRequestArguments = { @@ -512,41 +506,40 @@ 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; + if (restart) { 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; - prompt = vscode.window.showErrorMessage<MyMessageItem>( + this.hasServerFatallyCrashedTooManyTimes = true; + prompt = vscode.window.showErrorMessage( localize('serverDiedAfterStart', 'The TypeScript language service died 5 times right after it got started. The service will not be restarted.'), - { - title: localize('serverDiedReportIssue', 'Report Issue'), - id: MessageAction.reportIssue, - }); + reportIssueItem); + /* __GDPR__ "serviceExited" : { "${include}": [ @@ -557,22 +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 && item.id === MessageAction.reportIssue) { - return vscode.commands.executeCommand('workbench.action.openIssueReporter'); - } - 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); } @@ -720,9 +723,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType } private fatalError(command: string, error: unknown): void { - if (!(error instanceof TypeScriptServerError)) { - console.log('fdasfasdf'); - } /* __GDPR__ "fatalError" : { "${include}": [ @@ -732,7 +732,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - this.logTelemetry('fatalError', { command, ...(error instanceof TypeScriptServerError ? error.telemetry : {}) }); + this.logTelemetry('fatalError', { ...(error instanceof TypeScriptServerError ? error.telemetry : { command }) }); console.error(`A non-recoverable error occured while executing tsserver command: ${command}`); if (error instanceof TypeScriptServerError && error.serverErrorText) { console.error(error.serverErrorText); @@ -741,6 +741,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType if (this.serverState.type === ServerState.Type.Running) { this.info('Killing TS Server'); this.serverState.server.kill(); + if (error instanceof TypeScriptServerError) { + this.serverState = new ServerState.Errored(error); + } } } @@ -870,6 +873,33 @@ export default class TypeScriptServiceClient extends Disposable implements IType } } +function getReportIssueArgsForError(error: TypeScriptServerError): { extensionId: string, issueTitle: string, issueBody: string } | undefined { + if (!error.serverStack || !error.serverMessage) { + return undefined; + } + + // 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} + +**Steps to reproduce crash** + +1. +2. +3. + +**TS Server Error Stack** + +\`\`\` +${error.serverStack} +\`\`\``, + }; +} + function getDignosticsKind(event: Proto.Event) { switch (event.event) { case 'syntaxDiag': return DiagnosticKind.Syntax; diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index 4a1c217471d..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); @@ -65,6 +66,10 @@ export default class API { public readonly fullVersionString: string, ) { } + public eq(other: API): boolean { + return semver.eq(this.version, other.version); + } + public gte(other: API): boolean { return semver.gte(this.version, other.version); } diff --git a/extensions/typescript-language-features/src/utils/configuration.ts b/extensions/typescript-language-features/src/utils/configuration.ts index 25315feab3c..f42b0c780b2 100644 --- a/extensions/typescript-language-features/src/utils/configuration.ts +++ b/extensions/typescript-language-features/src/utils/configuration.ts @@ -2,10 +2,12 @@ * 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 arrays from './arrays'; + import * as os from 'os'; import * as path from 'path'; +import * as vscode from 'vscode'; +import * as objects from '../utils/objects'; +import * as arrays from './arrays'; export enum TsServerLogLevel { Off, @@ -50,13 +52,14 @@ export class TypeScriptServiceConfiguration { public readonly localTsdk: string | null; public readonly npmLocation: string | null; public readonly tsServerLogLevel: TsServerLogLevel = TsServerLogLevel.Off; - public readonly tsServerPluginPaths: string[]; + public readonly tsServerPluginPaths: readonly string[]; public readonly checkJs: boolean; public readonly experimentalDecorators: boolean; public readonly disableAutomaticTypeAcquisition: boolean; public readonly useSeparateSyntaxServer: boolean; public readonly enableProjectDiagnostics: boolean; public readonly maxTsServerMemory: number; + public readonly watchOptions: protocol.WatchOptions | undefined; public static loadFromWorkspace(): TypeScriptServiceConfiguration { return new TypeScriptServiceConfiguration(); @@ -77,6 +80,7 @@ export class TypeScriptServiceConfiguration { this.useSeparateSyntaxServer = TypeScriptServiceConfiguration.readUseSeparateSyntaxServer(configuration); this.enableProjectDiagnostics = TypeScriptServiceConfiguration.readEnableProjectDiagnostics(configuration); this.maxTsServerMemory = TypeScriptServiceConfiguration.readMaxTsServerMemory(configuration); + this.watchOptions = TypeScriptServiceConfiguration.readWatchOptions(configuration); } public isEqualTo(other: TypeScriptServiceConfiguration): boolean { @@ -91,7 +95,8 @@ export class TypeScriptServiceConfiguration { && arrays.equals(this.tsServerPluginPaths, other.tsServerPluginPaths) && this.useSeparateSyntaxServer === other.useSeparateSyntaxServer && this.enableProjectDiagnostics === other.enableProjectDiagnostics - && this.maxTsServerMemory === other.maxTsServerMemory; + && this.maxTsServerMemory === other.maxTsServerMemory + && objects.equals(this.watchOptions, other.watchOptions); } private static fixPathPrefixes(inspectValue: string): string { @@ -157,6 +162,10 @@ export class TypeScriptServiceConfiguration { return configuration.get<boolean>('typescript.tsserver.experimental.enableProjectDiagnostics', false); } + private static readWatchOptions(configuration: vscode.WorkspaceConfiguration): protocol.WatchOptions | undefined { + return configuration.get<protocol.WatchOptions>('typescript.tsserver.watchOptions'); + } + private static readMaxTsServerMemory(configuration: vscode.WorkspaceConfiguration): number { const defaultMaxMemory = 3072; const minimumMaxMemory = 128; 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/objects.ts b/extensions/typescript-language-features/src/utils/objects.ts new file mode 100644 index 00000000000..a31467bd8d6 --- /dev/null +++ b/extensions/typescript-language-features/src/utils/objects.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 * as array from './arrays'; + +export function equals(one: any, other: any): boolean { + if (one === other) { + return true; + } + if (one === null || one === undefined || other === null || other === undefined) { + return false; + } + if (typeof one !== typeof other) { + return false; + } + if (typeof one !== 'object') { + return false; + } + if (Array.isArray(one) !== Array.isArray(other)) { + return false; + } + + if (Array.isArray(one)) { + return array.equals(one, other, equals); + } else { + const oneKeys: string[] = []; + for (const key in one) { + oneKeys.push(key); + } + oneKeys.sort(); + const otherKeys: string[] = []; + for (const key in other) { + otherKeys.push(key); + } + otherKeys.sort(); + if (!array.equals(oneKeys, otherKeys)) { + return false; + } + return oneKeys.every(key => equals(one[key], other[key])); + } +} 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 20bae2ab64d..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.path === version.path - ? '• ' - : '') + 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 2864d9f9273..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', @@ -21,11 +21,16 @@ export const enum TypeScriptVersionSource { } export class TypeScriptVersion { + + public readonly apiVersion: API | undefined; + constructor( public readonly source: TypeScriptVersionSource, public readonly path: string, private readonly _pathLabel?: string - ) { } + ) { + this.apiVersion = TypeScriptVersion.getApiVersion(this.tsServerPath); + } public get tsServerPath(): string { return path.join(this.path, 'tsserver.js'); @@ -39,8 +44,28 @@ export class TypeScriptVersion { return this.apiVersion !== undefined; } - public get apiVersion(): API | undefined { - const version = this.getTypeScriptVersion(this.tsServerPath); + public eq(other: TypeScriptVersion): boolean { + if (this.path !== other.path) { + return false; + } + + if (this.apiVersion === other.apiVersion) { + return true; + } + if (!this.apiVersion || !other.apiVersion) { + return false; + } + return this.apiVersion.eq(other.apiVersion); + } + + public get displayName(): string { + const version = this.apiVersion; + return version ? version.displayName : localize( + 'couldNotLoadTsVersion', 'Could not load the TypeScript version at this path'); + } + + public static getApiVersion(serverPath: string): API | undefined { + const version = TypeScriptVersion.getTypeScriptVersion(serverPath); if (version) { return version; } @@ -54,13 +79,7 @@ export class TypeScriptVersion { return undefined; } - public get displayName(): string { - const version = this.apiVersion; - return version ? version.displayName : localize( - 'couldNotLoadTsVersion', 'Could not load the TypeScript version at this path'); - } - - private getTypeScriptVersion(serverPath: string): API | undefined { + private static getTypeScriptVersion(serverPath: string): API | undefined { if (!fs.existsSync(serverPath)) { return undefined; } @@ -208,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 65013b5a751..57e8c4c5f7d 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -393,10 +393,10 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -jsonc-parser@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" - integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== jsprim@^1.2.2: version "1.4.1" @@ -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.6: - version "0.6.6" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.6.tgz#2139d8c6fb9da73cbfcd25fb6a4459374fb89d27" - integrity sha512-gVip+I1fMBdvSuGM+OmVywe84F8qp0AmMi/Aj35ggqSez8CWh/WpG7vuz64QtVKqgnwvFh3jSCmXFml5JBN2mw== +typescript-vscode-sh-plugin@^0.6.13: + version "0.6.13" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.13.tgz#96d92976c25d36dfa5761230a02f1b217046e947" + integrity sha512-hl6EkNtH90Cn6c5xAmvCn5YpvMzVedqMhUxxe+FWxX/xE0qT8ef1q2vyNlRnUGW95Q6A//5Yl07p9xkSrZi5hw== uri-js@^4.2.2: version "4.2.2" @@ -667,7 +667,7 @@ vscode-extension-telemetry@0.1.1: dependencies: applicationinsights "1.0.8" -vscode-nls@^4.0.0: +vscode-nls@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== diff --git a/extensions/vb/package.json b/extensions/vb/package.json index f41732e9d6e..29e3cceedb6 100644 --- a/extensions/vb/package.json +++ b/extensions/vb/package.json @@ -23,7 +23,7 @@ }], "snippets": [{ "language": "vb", - "path": "./snippets/vb.json" + "path": "./snippets/vb.code-snippets" }] } } diff --git a/extensions/vb/snippets/vb.json b/extensions/vb/snippets/vb.code-snippets similarity index 100% rename from extensions/vb/snippets/vb.json rename to extensions/vb/snippets/vb.code-snippets diff --git a/extensions/vscode-account/media/auth.css b/extensions/vscode-account/media/auth.css index e87a6372763..45c42c75ad5 100644 --- a/extensions/vscode-account/media/auth.css +++ b/extensions/vscode-account/media/auth.css @@ -16,14 +16,14 @@ body { flex-direction: column; color: white; font-family: "Segoe UI","Helvetica Neue","Helvetica",Arial,sans-serif; - background-color: #373277; + background-color: #2C2C32; } .branding { - background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHN0eWxlPi5pY29uLWNhbnZhcy10cmFuc3BhcmVudHtmaWxsOiNmNmY2ZjY7b3BhY2l0eTowO30uaWNvbi13aGl0ZXtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5CcmFuZFZpc3VhbFN0dWRpb0NvZGUyMDE3UlRXXzI0eF93aGl0ZV8yNHg8L3RpdGxlPjxwYXRoIGNsYXNzPSJpY29uLWNhbnZhcy10cmFuc3BhcmVudCIgZD0iTTI0LDBWMjRIMFYwWiIvPjxwYXRoIGNsYXNzPSJpY29uLXdoaXRlIiBkPSJNMjQsMi41VjIxLjVMMTgsMjQsMCwxOC41di0uNTYxbDE4LDEuNTQ1VjBaTTEsMTMuMTExLDQuMzg1LDEwLDEsNi44ODlsMS40MTgtLjgyN0w1Ljg1Myw4LjY1LDEyLDNsMywxLjQ1NlYxNS41NDRMMTIsMTcsNS44NTMsMTEuMzUsMi40MTksMTMuOTM5Wk03LjY0NCwxMCwxMiwxMy4yODNWNi43MTdaIi8+PC9zdmc+"); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAlqADAAQAAAABAAAAlgAAAADkcSUjAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAxaElEQVR4Ae19CbgdRbXu6j2cecoECTIkICCGzAg+7qeQ9544QFQgiXpVEJTEe59ALsbMwE5AMZCQELgKeSoqDlyiQogCSUAC6FNCQhIwQMALCbNMGc68p37/v7prnz47++yzzzl76OCu851d3dXV1VXVf69atWqtVZaUg397IPJwSCKT46zgx16wh+19XSYnO6PjE7H4MDuZ7BSxXhQJ/On5KTWbTSMm3bYlvHXmJNxj2SatFLFVioeWn5lDDwAgMvOU2NG/3DMo2h691rLtr4bqBtVX1NVJRUOVBIIiyYSI3RkXO9q5NZmU5S98tu7XpmQF2AwAzCoNwMrAMm/CT3HkrgqJTI/KwodOD4ZDa5MVdUPtfe+KxONKiSob6+SIE46XqsYGK5FIBoLVtQEJiSRaWp+xktaNuzpqfirTLcBOZNJtdnjrDCk6wMrA8hOgWJcZoFSrT4nJogfPESu4Dv8AVLTTEqsC9MkSDHB2NIYfkZGnjJOqpiZQrkTCAmWyKqtDgXBAEs0tLyZtWRl7953/u/uiUR0sVgH2xuKERCJJnhc6lIFV6B7OuXyAJrIpqDzVlQ99TUKVt0scAEomQaXskBAONlEFrgpvLdkZk6r6Wjn6IxMx2qVeY0IsSVrhqnCgMiiJA62v4a5Vkmz/4a7PDWtmVXSIfGMSAGYVFGCpGvGh5VCiHohEAiJXi77sKx+eLZU1N0hHCyuTAJCCDqCAKIUCYxv4AcY6YnL0qeOkZuhQSeoo6b5OG+AC4ZJQRThQFQbAWt7Bvf8ZSNg3P3deA8ZUF2AzADCrMAArA4u9XMoQsQMp6nHlH5dKdcMcad+fJGVCCKRAxfMU1XKB1dYpI8acKI0jj5FkDNSti3LpzSgjCQQmrFA4HKiuIMAO2EnrtkBb+8pdXxr2OjOd+bAd2vQ2cro8mXPjwH/LwBp4H/a/hLvuCsr06cpky6KHfiK1jRdJ235M80ClABMtmMMfKJQDNB4j1VCstg4ZcTKANWpkZmCxAJZiABYIhgO1VRwi21HGj63OxI27pje9xGz5BlgZWOzVUgSPjEqufOheqWmcIq37YwBCWKsDDGlQULnAUh6L4CLFAh/fCmCNOSE7sNxi3IilxiUQCAdrayR+oCWBcn4q0eSy56c2PMc80+6yg2t4MEAKVgYWO7HYwcz8Ig9XSTz5kNQ0nC7tB6KoBmZ+bnAZdaVUhIMHVDxWQtQCYI3thWKZ8rrHLICiCwCslmIKFGn/GoPm9S+cW7ddswJgZw4Ta9NkC/n6HsrA6nufDewOV/Ap8x8cggHvMUztTpLO5igkCV2g4hMIJI3dY56mqBdwgXOlWP0DlhbNkhVgtoQDELza7Z1iJ2J3J2P2DX8/r+Evmsm2A2dukkBfAYbZSDkUrQco+IQ0XeY9eKwEraekovYkzP4OBhUrlGLEXYB1q6QnzXPYLUtuJ5SLcei1k62tMYDKDtTWnRusrf5/J9zbcv9xa5snc9aooLJti3xYbsVy1lEOxekBI01f8NBECVnbJVx1hHS2YSrnGf4y1SQTwAyYTJzpvr6lAWA2AWYlW1vidrQjGait/VS4pvaPx9/bvOn43+4/GwCzDcAoC0P+rKNd1ot9q1s5d489YIa/BQ9+Ap/yAxKsgJSJ8gGXUc94I1BD4Og/j91zDodYGFTmvf88VsYnpiWSt7ICNXWcoQoo2uNJ217298/X/8bky7ZcVKZYppcKE0OaDg0FDn9XPfwFCYc3YEYGUEUpTQeFUNTgyZniDBUyfJdecu9hVJjAYS+YbGuJ8z9QXXNaqL5uzQlrm3d88O79X+Ujt860MIu17ExDZJliFealkMJYMn1NQNZATnXlQ9+ScPXNEm3H02zKrZQK9PpoxQ5+PBJ3pVwqbkBJpFhjPiSNx47sWY7V60Nyy4BaQDQhdqCyJgRWX5LNLbuA8xUfaKz7sTtEkkhhMuBoU5QpVm792rdclKbzkyWoFj64WCrrAKo2SNMxhukSjb4CvoZe/pGBebyfP88ZTNx14KQX6BdV4McQSna2JZItLXGrsurEUEPdra8daPnv43934CIy+Qoqth2hDKx8v4hpkKbrAi++3AUbf4AlmqukvTmuiytgWfr3OBdg/bs533cpwMDgJ6BFEQtUVB4dGlz/kxPuab5TH8S2A1z9bGi+6/o+KY/8FKkUw6KH/ktqm/4Ngk8y6UHltvVCH39IrQx1SsXmoI9l5Tc72xS2Y50AWGtnaEjdF46/p3mtPuJqqmOUQ356YMZtYai8xITrfzsGb4Dg8392W6IZ8FOygImXslwe8KOzF0Dti2D8vZbO0KC6z37wdwfm/d2yvu8dvbPfXr7acw8YcULkrw0Sa34EoBovHZCmq3Jez7dlvwKkGMCoqIHn+E8tQtse5n1UwZn37HXVq0nOeO14sj0Ws44tD4U59FjWLEaaPue+IyXWskMqa11QUfCZQgaK6OtxhqeaIkpInjLUyiRBjJKMhRrrqkOh5IXlodB0S3/ilDR9/cngyx+RcOVg6WjtXZqe67OUUqVnJroQsgu+nTzF/oXUlsIUTIk/WaZY/e18A6r5D34MXbkV0vTBEoVKZ1Zpen8flv2+FCHLnq0YV4N2Zwcwb48pU6z+dDfVXiKnRKGh8HksJt+tIp44penZlmj68yBzD6DjRQ8pmQbwWR4u2aSau0oQg2JRT9EaUqZYfep9vEaKFGhFs2DDN6Si8m594Yk4RQyF+0gVMRlgwyTzb9qRIZu51C02+Uzc7WLaSS55eIvJh1WHwnVGWt0O+VM1eFgMg4dIXBY8OF+qar8HlRd0JciHRRutEoXUy/Q836R5kjIemnwmzpjJTcwlD7O6+crAytaZ5ppK013B5/wNy6W6/goIPkmlMBD1V5puCu8h5gs66GV6E7zHPZRRsmSrgOS7ZI3K84O9Bg/zN/wcuulfVYMHZ+3Mw+Hk+bmmuHT8pPgrZki/aG4qfVymWNneAfmp6Y5TDpm34T6s+306v9L0bA/ntSzAMZdK45qht4qXKVaPPeTM/GISWVcjnZV/xPB3mho8WL1ofPZY4AAuGHmWAZMpKv3cpPsgLlOsTC/BLNHMXn+YRAOPglE/ERoK3a1oMt1XrDQdDsne4YEEl/kv1vOzPcetUxlY6Z3kLNFEZdGG42Gk/hh00w9Xg4fedNPTy8n7OdBjgKRl+5RcudUqy7G8ADDS9DkPnApQbYNjjsOlsz1/SzTeZ/V2bKiQiZk/HUvp572VWcTrfaNYVLddvTUkgyYl5Rk2c1NARtRbUkIHX3nrKyNNn/fgp6BCcL8E0DUxuHShNL3ULzBFqVCR1KwwlZi3LshnQbkDizMkS61i2dkmqDa2zMQpr7tuDc3FQyOmNH0TtD4hTZ97/1ckFLxDEniBCbpvUZMofzQjHdzp5/6oZaoWuQHLMLOrXmgQO34hvugzUMJgfMnvIH5I9rXfgRfTJpqv9P4vU63r7cAYPESmx2X+A7NgQLoCtn68q7BLNL3VK3U9DT1pp6lsPjzoBVj8mqFWSzOfW549B7ZwP4W67RB1fgm7ACh24T84DZTsKlm2/WKZOX499Z1l512OdYoPG5yqkho8qBAoIXMf+K5UNiyAch4axbdXwiWaVAU9BwZQqdHPJCAPD82/55aSHbp17BlY/JrXQLVmOoa/VTtnSqjqVuFI2LI3itkJmX6nCLrICVceIRVVD8iNTy2RK6yrtVGGypWshVke7Bg8kCqJzF2/Gk45LoGMynEf5HGPl6WEIl/yAIlPTjstcmWyP86tW+ZZoWvCo65sbvrblVLVcCuGCFtiUb6MCjQshH/oOquAtQJMbgKzp6TUD7lKVjy9Ua57bJAaaUZ2dnd0kb1KxblKXtAYPMxd/zss0VwibTB4sEGlaF7sl8AXZP61TjhxX1qqirYO2alTPx0c3JHetbGbdq6SmqZLpXUfAEUnAo7NWA8NYLNjkFBXQJj4NpxkTpMrJj6ixgXPTLNTXut6uLkoyTR4WD0zpg5kB7+3EbrpZ6g0veQyqh5abyTuRBSP1dsM8iYwnMBTn91ph4afdKw0jDrWtqForq+oh6KKmWyDoegOLO/MbuXOO6V20BekdS9ngRwyu+ftsaZ2DPIfmI8je7Rtvlwx9vuatdRDo/FJNe/3gzDZg8FD3RjH4KE/SzTsinTy0WOH9OFCWrl8hD4GPwZYSSj/wmzBqm2y7Ff23H7cZ856Lzxk6LfjLc30nFw69R1PKwmsrqGQL57iAg6DK3c+CCadoOIyhnoh8dzXy6EVhvvohMQ7bQyN18mNf1sHUURdSYdGCj6pnDd349H4RmDwUD0AULH5hQBVT+WaZwF0YEYkGA5KKGRZrfsWyS++fHGgtvofYEpArArjpLaXl93jZYd5N9SE4oTkM5vAd0yQln1k0vvLI/HLsZXa1TbCX7n1nNyw43z5zujHBZ7iVLhaYHfQqRYbafrcB8ZhDHkEk5BG6YD7oP63LVV04Q8AKhIxGuZjsAOVBZtx4C1JJKYmb/78Y/r8aKLKrsYR1A0NBAtfryxPcIkuPPbajjeUVTuOhBcUfM11E6Aa4iy4sqb9/4fPJVCvNrhADFd+QCqr/irLd8zSCYFjht3zjDRLvft0SakwdniYd/+ZuG+LhMKNKk03fj77VFiJMiuDjnGwuoG860a4ED1RAKojbttSwxrB7zYGHuc1MS55AF4IGUjLIUO46RksuNp/lYpqmC/Bw1x+mVln1mhB07Ju6AoMjf8iL738FTy3UzhrjIzm8/IfyKjTfdCc+6bC1eYa/Z7jMcfggS33BsPamDjTtfQ0c55eFtO95XiPzT0m5jUGU0Z6XhvyQ3g6ViXVtr2LZeWUiOZH2044YVL0dT3x5w8oFhys2rIeqiEwX2rPN6hMqx0n+G37YuDdpsqoY56V67eNV1DphCHSxeuZO/obU/7GMjn7m/PANyFNX6MC3XiCzvgpJjk4mDQTe3P0lMb0TNd4rzfde+wt1+TzXk8d66gWxUYC8P4f2yvtLWfJjQAV9e7JA7NtPg8haTzsPzD7GyUt72GbMqksYH35PWJoxDBbUTUK85ptsvypb8q3x96mz/TOSPtbCTV4wGvlJGTu+kWwSr7GFwYPfWoP51RoA4e+1n2PSqdMlZs/97aKSCKngOJGUvDrXiyTDQnsfqX4Z7bOCqc6DsHwNRcnYGjsgJQbHVE3+FZZ/vQdcC7u7CGDnar6XQUtI+L4aJpz/01gdK8BTwL5Gx9Egwd2vJ//teUY+kIBsCRBgGqpLPvMGXLzZxxQcVbrNCCti5wFBCfRL+2juMGW0QoseAzRd8D30O2fzUlP6+nc5DWxN583jUMSCm2Dw/y6QV+R007aKUu3nqRrkpxMcDjrS6BQ10jT59z/K6luvAyg4syPQ6wLqr4UWIq8Nih5NYe+Vuk48FmAap72Az8YB1Q9VwrdfNA3w9wm3RybOD2d5yakX2N6+nWTZtLNPeYc17lXC10yp93NcxM8uU1Sj7HJa2JvRm+aHjueelsh1qiqOREzx7/Jsh0X6mSC7gY5NOYSyKRz2xC24zv3rccQ8iXsRYOv2/ECnEsRpc2jQ19ch77Ols0ST5woy6as06GPn5f5YLJV0tu1Jp83zRybuKc83uvm2Bt7jzOVwTTmwT9dGu6ERS+TvDSV58UKFViH5EaNAfB6PwXftVofTD6J4oJsQaXpYGRnr6+V79y/GUs0ZykPR9/lbgN9G7NdNjeoDGLoqw2h3jfJDWefJiumvJbaszBX0VTf6Hu2Hs3bNQwT1m+kgiIRiB1KFSxYZFNLghoGdYMvkRuf3iHXbzlOxQUEV6ahcZorTZ/1wAjoiO3AyzkFwx+c8XORHKjiv5+DLRz6sA8h9nZuOzBdbvjMLK1uLkOfn9vl1g3A2r8CM8I9mEFV4qU4MiXvOzHH3th7zIK85+nH5rynfCYdW2ugnJBK/Ctrx0qw6hm5fvsXFFwcGimxN4HS9DUQfH573YfwPTwFg4fjpJM7PKjmhcnlV3Bx1ucOfe07xI6dJDees0Y4pNPFRy5DX1cLfXsUkCtOxxZjyU/Br9M+fEFUiekClwFFeszmpKeZc+8102xeM9dNbPJ5z538FbrkEghWQOZ1pyx7apUmczcqUi/VTQeoZq37H3g9T8J90FDpgMGDoVR8kLdMf1EuZ8ivqgthdWO1LPv0eFn22ZecoY+yKX+syjivYWC/kLxjFjYba3k37RwHbYQ/gU85qgDS977WMgyhZhL1SGJovFSWPf1ReIs7T2aOe1ULunTt2Vgp+L0EQcSiEF1YWDqiSoluDwKGg++HE0vDexhwmfO+1iYv+S0ubVHUksTQ9zVZfvYdWqzK71SU0L+nOFIv517vB9W/0gZ+l9vHkOSCtyIluHz0yxK1x0GV5GmdoeCVDfwpAyqBogJ82Zg1Vtd/RIKBF8B7nSUX/OJf4drz90qVYjHOBimecKgUY90ShOfuMaJU8B6nEgt+gFePpZlqLCBH25/DBzNaQUVAkXfMhwEK21Wath3ceW5d+PIkxSTPH7tXmk+eiOn6I1Dwo2ZDqcHF2nHxNY79jaskWLleRh33S2irQqVQfVIFHSDh3XHWrpQJLdPGeWMee/9ZbFECJkSY7VbVYcWh+eey7AmAaspz+iETUO4uDgOuSUkpcebad8mKuGCrZFlnh2fKsr/9FtP/86TNVfSDWkbmIoqQylljPEaib0lTExaGwra8/lpQosB9GE1QIOEqPxPW0tSUMa9pwEnqnIkmk3s5/1EUCo8V0E0TiBJmyvJzVusj2Mfs6/d5cCiWaSS/IkqxGWaffD70qWBoMIgyIcq4Uq9Irxfzx3myA5t43JKamoCMHClSXw+aSlkoMpihzwyFhKFJ1/vdPE5ZXdfy3Q5oTqFI6E5BRTvW8SK2lx+voNKPNgLWA338TxC6A4sNphRbjSkw/s8+eaa07fsurFhI2dhhfF2lDWTQoaggQVTpA0eJHHY4XiPOwetrDVNgwrnqiBtApRDl5DOtYP78BQ5vAlCFsSxzl7zcClHClB2iyoYAVARrmf8koWso9DZYFfHwdX0Y9oHTRy/CrOxt8AkrIZJALjpcpm51Xl+I9+m9H3MU05UQxEOHiVRXibwO7SRSrwoOjahbgJkQWE0e8oVztmji9Fkj0wcWMPRVVKiKTtuBy+XGsx0xiVKqyYXlVdlG/vshuN2YGVisoH5dnLWoOOImyJPeAc/wC3QcGGa4xnUMLErfFFaltk5k5CiRN94QaW4GuCBrVPGDCyRvLfkC2Hj+6zEO9Nh9MzzOOWhm3hhXteGOlldBTc/HssxmxzrpGbsYQ182omua21OT3FZrFzBPT+e85u0ak8+km3OVxOGkZ2DxDn7iEXQaxREzx/4SDD1M6m3sEAqO2ewQakrU/O6Pvije7p5785hrvGSu89jk8V7PlMdcN/eS0pih8UgMje++LfI2qoklOLXUZkt1wOcDkFfv1x8cI874Vkw+VgCB2RlMHXmsRejegwGIZ8JYjlon1rtfkhUXtOrQNx1C3CIFVstbtfTHZrtm8qbnST9nvkxp6enMw/+DeSzmTA86Y4Qa8eyT16NHT8UMrRVGCWTqM89uTA3MU7zlmWtMM9fT09Lzm3zmHhOn0vGWafJPkAw9TOQoAIygUb8eyJxi6HE9xXchnflT/ywUQctkunPaleY552HStZgJhi3M+uZBNgVVF4CKSzOR4oGKVTG457FfQm7AYm2pm04d9dknPwFqMB4znjexvkjtg6J9mVk7jUBiIPWqw2zxmFGC2SNqh+oRPBnB5QJIQcRjPXDKMYBzzry/zARVH7Q9EXsL1uEfx1rfUkdZ8dBQG/Y2plDHuQOLNTDgumL03/Gmxkln6y61fPYLuFhHMzSGgfkjj3aYezL1pGgMBjA8V+rlUjoDKr3uZE3l7zoFakHLqDbc0bIRVk0nyopzHtO1Pi4eF8ukras+vj3qG7DYDIKLPNd3xr0lweYJ4C0ehx2iX6T0TkcTXAZIFEccBYBxFqgyL2QheMyQmKJkSNc0FpGJenHoCwWx3hfAWl8EVOosWXnuPh36etPwZJH/ZKEX5r2H3lCeC7PFKyzunv1Ruf6pP0BK/xnIvEAaqFvFt1jqgCpw0OLQWN8gMhLKjG+8JtIK/1ecNbKGJGLpNeU9BCbTnWOagsZgMQOFRGiAWPHpECVsVIuZnR+Gh8PpmflM3F6UwDawnvz3S0Bd+k6xTOW5eG10pOaMPRuLxXCuTyl90hkuTL6Sx0AIlxXDIKpHjRQZMsShXPTa56VcqSHQTdcXBbJnQ2BWSWPR1kelDUPfcoBKVXci9vtFd6oQr6j/wGJtqCNFjUeGOWMuxBLQcixekwriO/KRLwHv0Hj4CPBeRzqgov0qAaT8Fqps+C4m2kkYY0BmEa4KghIvlZXnnCG3nveWZ+hT6LHp5XBwD/RvKPSWo0wrZkMMc6zZGBbfgT+t66DRyY6nkagDPM1Qwh+Ci4FDYwMWsisprcfQ2IGhkYw+KRazONloLIq1vvZWiFa+BJP2dchgybQ1MBYt8dCnjfD/z8AolmkfZ0NXA0JcvpgDt0Wdzd/Al45XFODSD4dG/wQCjOCiAcnRI0WaBrtMPb8D12KmAgvIHa2bpS3+IQUVhz6G94nasLalwD8Dp1imgo5ukSulH/NjuQGUywrdA78JQch7yGLmB8TmeQONCS6CbPgRWDSGu5Z/vAF+CkNfTS1mfXtXya1TL9dHqCXQIaLmwm/DBFJe7znT09PMuYl7upfXGdLLc1K7/7p58/+yOWNUccTYtVhS+QgsaPaBcPE5BJd/gndoHDTYlpHHBeB36oC89drZCirObA8VixkltmldmwkE6Wnm3MSmiEzn6Wkmb3rMfPjPP7D4oK3u0/78Z5FX9jhMsVq551o79/5iRDo0gomvAs818liR8ad1PXVa12H5qA89AKqVf2BxrYwCw5lrPieHD31C2jub5OUXk2CCAw7L5UNwcUxMQKHLCjTAKvsPct2Om5Bkq24aqW859LkH8ggsDB1k3uli59/v/jqMYO9Rvj2AmWFHR0BefglMMhza0LKGMzD/hQDUgRyj2fpBl8l12zdL5K9HpuwBVHTvv0r7tUb5AZb6bVoMYEFL8lv3zINu0o/AsBM9CTCMWAbBHCEOATXB1Y7pvU4WcZk5/PXfZTRLy6Cq6ufke09+VsHFehq1bb++TR/Va+CzQjK4EXe/5MvWLoerSeyXDPdBAVCwpOvFlxQqRHBhJrZnt7M4TOU8MzPzUYe4VaG4gS6FatGetfK97ddjaJyLa47RrB+NIQh8PwTOClGXgVEsgsrIdi5f+zOs+l8Bwahj7UuzJz5E//mDgDVcPX9lt8iB/Y7euj+HRdbWMZqNtiakfvAc8F2PSmTHYV1Do7aE+UobONf2C6jYE6gL33b/gUV+KgWqdX+AT6oLoOnARWhnhwcDKM669JhPRSCPxf/XXhHZS01PHPuqZ7SW5of9E3CNZj8mVbJLrtl+loIr4oojTM4Sxn7CFbuB9enfUKhakpNj8h93VUuyGvsl130UVilw323R94MTCCg9YYygp7hI1RQaOpB6vQEDCA6H1PrkOp03v97kgx/HnpL6V1CbqWgCv7hevrttsSy0Ilq7EgtQ2Wump33QW6kq9B1YpiMvvW8YhJ+PqdO0Ds9+yYoj/Ojam56kHqYHmgRwMaZFzVtvOuAaNtzBlR/B5bSAGqPYGAFbi9QPuVq+t+NfJJqcJpEJ+4QuLrlDWjmkeqBvQ6HZ4eGydcdLAO6DKqpPhI4SVZOp6OeARYHhAsekKZjwo8OiJ+Z16kbRAOLNV3nm5NElO5RhyvJLTHea3MiJ/iRq6v+3VFjPy7VPflxBxRmj2mNqK/7pf3IHltnh4fJ7P4IZ3zZoUg6Hdxp+pQ6oTFfqEOieKKBwzDh17AKLwyH/mR/meLJ/L7QN9jhDIvdB9CtTr0qMNneIiKEPhmH7lEfku9vnOoa+WIw3C9amP4oR+/AbzA1Y7Cxanlz++08CCI9DRbcWPgnIqGeWSnvBpR0L8JiYh/qPH+bTf6RREa8FNoFcAqKtoJ/B5bQFewZhO7141IaD3u9DJLFOZu+o1VUHP26np3Uu3k8vwDLSdCzRzFr7ZciiHgAQsPwBuyobfEW2LyVFokxjCCIee8BkQMV02v8RXNSPenk3zOYxwhpBKm/LFkw9TB6eDySY+03MsjIeu3I6qmTXYM+gwfYuWbLlNLULoHZtUYdGVtD8pzc+vfLefOnXvPem5zN5M6V3v9YzsChNp2Ibpemz7p0l4dpfqOeUJFSPnQ0wvTXo4ViR5FxLgYqnBlxpMTuGSnc02SK4OqBST3AZIwfTnvTYeUL3fk3P05dzlsf8DOa+no45a6QzXe4ZFKr6APjOv8o12y4v6p5BWlHvj6m0idMrb/Jma6S5Zu5Nvyc9vft5ZmDpEk3E0em+7N5rIX1eIdFWzGyVq6bgKfdAqmSCF1wpapUJXHgEh8NXXoILoBa/C1JN6+CyqBOUPI7t9AatxKzxLrn0vkp1bPdPODQeDCxdook4QqVZa2/D7GchZDjcL5mwQH7zFfQh5p0KKjc2YPOCy0vF+Koo5+KzXtkNfwz7HHBpAp/r2wALJdSNGyPUNE6DEuGzsuTJcY49JgTKzpYsvq18PivWHVjdpOlrfwvd9RmuM/787Jes4HIB6QUaW6Tgw4+Czb1IqTyZ+NdeFtn3btKR0pMZ85GhxsFvg5V39wyqHgWB6na5ZutMZSnoaIV9XKrQn2+yP/egfV2NNNJ0xjXDN0ql2S8Z0vR8BgLHK0pgxV0cOY/xnDAflU8tGIu+9aYzWRgCt0UJOF9znMH1bVjOZzt6L8vZM4h9zD2Drt3+LxIf97WUz9d8LWSbBYtcAZBrPm/7+noP8jsUS5XzoEc16+4mqR6+RSrqzkjtl8xCc/lnRUw+7zHTTEgde8Bz0LCIzLys/5oPFjPwkxCQDnn+2bPgYO1fwSTjOrh6G4Ya5pn+jDE0grpS5lXb9FUJP7VTIls+5Kw1gnL5wrDXvJz8xpj1oYFUzrt83dGSCMAZf81Y8FQHO+Pv7bkp0CCj95j3mZdujhl7yZSCCyDSYZCXcBzACwnAKLYGxqLxts2QPxwvd39zo8w/+ddQGDxHqR73Rwa3rMX59kc3RjBD44cw690JvusCHRqppVrKobGAfeaIEy5feyImfNg2pOooSNO7lmgK+GAt2lArnihxYkyAYeijAUYl9pjpaF4lt00/TX70lVfVWJSqwgvH/AE5T8eQ2AEzMw7n7jpdOqJxJWPIlC/XtPQCM92XMQ/ccbt7BtU1/QwiidWai+Kc96H6c0C+xh1W7fWQvzTBNVHxQGX6vhu4lGphjxnsLCrJKDZvmp4yw1KLGVBW9RsBd0oLxv4FIJyAOr+NZRXkx7ZsGviie/tnxvQ8uablcl9PecBv2VDj0D2DBl0i127bLpEnjtU26VIQBNLvkxCQ+v1XQJfqGHxNUEjHGthBHZ7eSQU4d7qTbChcLtLbMPaYSdgnyQ/Py7zHjHGntGDscxhaMHS3/re6asy0XYvBi/eFsQnpwdsscy0930DymHsdvha7fWEhu7J2HDZAfxYAm+bsSYih0fjDMHXoT2yeZervPc+WxmeZ6+bYe6+pS7Y0Nw+1PKfiq+dp1wzRFFC8OA5+Co72MfS1H1gtt5w7Xn54/otZ95gx7pTmjH5T4vvGw2nHFvVbZcDFupsO8B6bjjPXTOxtqzfNHDP2BpPONHOcLY+5tysvvdeg3dgzqKr+Llmy/SbNYvYMMvlzibvK7A4M3ptep2xpJq8pL/3ZmdLT09xzzgpH61INfSyYTMWMsYCDWV4Iin5JqDVfAFDN1PbopAJrlNmCDos2BI+TW2Th2FMByg3ujhrZ78tWZlGvweWTs2cQttNrugx81+MQS3xAh0blu3IbGv04ftIqJTOqC93BxtF+pTrafw6mYqNl1bl36CyJ03AytbkE406JM6xF4z8Jge6vAK6wTgDcvWNzKaaEefhxO0NjVd2pON4li7dOUXDx3ZC37CUwmwnmdZrYpDP2ppljE5t86efp96XnY34TzDFjSh//Bv/kvAYd4aIFGFzgj3vMdGCPmSHbR8stU59z/E4BUI4fiNwr43WntHDcl8G/rJJqgIu+rfwtpfe2EbNG1zKouv5eiWxbqv1Au4JeZo0GDObFegvNdC1bPnNvT/eZdJOPsTfNlM09oddgys7ruVEI5hxIsDj0VYKfAwFvb54Jby4XYg3NWeoYiMtFvgCqqZDaLRp/OcB1JZakKOci3YLDD1Ta7/9JWgbBRq6TlkGD5sjiJx+V2X/2n2VQDu8/IC17V2L42A1xA/z64KUXKujQR5eL2F4tFn0RBHI8+KnVOvSpNkWOQ1+2+tGdEgP5s0Xjr8WOW/8GgS9oozqOKCZFzlbL7NccXjfoWAY1fEzqq3fJVds+4YhZImARXF9k3lL89MGwXqhPQH56UQcsts6CuOE93ahR5UF5rimXXshhco+Z9pY1MrTzJLn5vB3gHypSi7PejhrIsUqzAVLKha4afytEF1NVpyug6hLFocoDqT/vdWR7mDU2O5ZBVdUb5KqtEYey4+Pxqj87n9JAn5i/+wkdBGdJZ9WUFyCQxA6rHS/hCyfDxSWd/AwdOuurgHYEFNw7sMfMLZ+frmrOpCrc17lQgcMqeZNF434LmdhkDDHc8zAEgB8iM0b2P3eOhWVQtD0J58FXy5VPbpBZ25oo83r++TfyqxyQ5/fgLOlwEfrm818Vq3U8KNc2LEI74BrYwwBNDH0sK9b5GmZ9p4FKrdJZTr6Gvt7qZ6T0V47ZBHP5SRCr7JMwpfQ+B5f71euXzaGRE522/di0oP4TUpvcJXM3f/z1mUdAhxvB5jDvv+BUiovQpCA3f+WADN1xCuRJD6oEnIx2/4KjdVCFPWY6WtZJdcuJcvO5m3XoUyZbFQn7V3Jf7zJS+oVjnsIEcTx2Z31FKqAtQUGqUmXi32f/rJjWCY1lHeFgCb/caTYGqnsY+OFHAnO3LGJXBMIWWBkeaU498MOPUyVTE4LLyI++dc+vAa4vYvji0EGpfPe85p70mGAM0CoCIR6dJ/953lI99patCUX+4bBICjbvsUFwB/kIpPRj8KL44fhzSCGgGBh3Axn41aQdsGqHWPZbr99+zLgT3gsNGvrtROuBBF5QrzIvLbPQP6jzwWDxOvr41t2rAK5LIWvijIp5s5FdKi9j6KvlUsVbYECnQpTwmA59o6fZUHArPZtJptfwXv8IboT68Bk6xPgRXClg4cALLvZiEjIVKDraiXBo2PB6qR9xmG1z59kMr7PQGMpYPup7MFCcocqRB91y7mUA1VVYLOVyDypuPCBrS1EmYwam47OiFL2zZSPoG4Y+BVWFOg7xA6hYTYKKlJOU66oJZ0rr/rtTS0CHgpReuxs/jmVQCPywI0JxztlC34SDKZapGgWN02H+RaB963ffxHreDzGzwteC3bNtBSTvZZOgkIehjzxkLBqRH5y7WIswWqmmPD/FNIfnFsUMS7bDYKRxBgSqFEVwKOm5T5i/WKELROxl/W7R06RW+g+KJXZnTIYdNVzqh9ONBprjVUEqVj0zPQd1660TIZB7GIaXkAv9+2+mADy3Y2gcgoVTp6E0dCCgOg5g1pe8WH4wbQNkLQHhHjMEpJ8DBY0RvjK8osg27HvduACCYjNcH0zJi90W1Ayd7DyVkbGtzAiswwAsfBeHELCchhnqM+OuRqkIXoD2noEL8LxvvY3GPCTh2B2yYnq7sx3ITH75bo84t/v210uVl2ybBf5wBfhDNAvrpqXeUUN70O1Gwt0w8OnAOhIUa8ShCiwio7dZXW/X/YsuUOVNDlVevA1uBCp+4Qz5HPd1NlyamucCrA53KDykgcXu5Rc+c3VI9g5KymhspE3m//UR2FptBumw+3mV5j0M+KlGHBHZ+ikM7/dj32sMP7EYKBcFqsUP2YCFncu681igWLQcTx8KWUZPzE62awNtLcru6bEDLfrQvJ+m8CpQ3X4q3tzD0PqogXYt5XjFB1cmYPXIvPtvKCw9k+onCBopfWT8ZkjksLzV+aYadqSk9KgsX3gx/k2/pD/LpPs8LgMr/QUZXfprJ2Bh3sK+123P6/JWIVWK0utgzg2ozPkhNL6UgWVemjemAJU8VwT7XjcMHQ8h8eNqqFEIlaKeyB9ngb0FD/ByyN1baXm9fgh9A3ltd26FRWysnVqcHWJW/OR9kNJ/GoLU4vBcBilGzGBil8/iAhkFpEMpIPXhrLBMsbJBzBhqKLAmcjN17HsNXXpHjdu8+mwl9P9apk9eKRR+0p9szvU6HlnK2G1xGVi9vXqvoUZk4oUA140w8KW2B2hGARfWDThYP++x1tckmNjNo9dK/ONWKdN3UeKa+fTxRtecC+pXbZkPcH0PqsPU5GdX5v8DZakMZgg0cZrkXYfC4Yf7bkkn/x3idMf775eAigBEXGFYcgo3U79EQtXQ7cSCKXX6CxIMunoo3KUOPVwtaXIZWH3qfi5Yu4YakQk/go3A51XafUi4U+pTQweWGSpIZWD1pwtVrwtS+iUT1ko8/nFVJaKtZKENNQyFMnF/6l7Ye2wrCIc62GukDKz+drSR0l8z6TG4s5yIPXbew9BIXXqKI7oYbgOCvsbeepl7vWneY/JfDMqH4bhEMRj2hAXjZxCsp8vAcl5J/34NuK4av1OsTpjPte+GQa5jqNG/ErvuUjDhVbmY6bqQ6cgFU6ZLRUyzEdRnbFIeKANroB1vloAiH31VAlEsAbVsd6T0/bZw8tQoJ1Qhvy8m99ysPZxs2d8mVujnZWB5XmO/D7kEREONyEcPyMkTTsEuFX+ERir2boRdZSGDUrVCPqAPZYMFCNbX84Yluy+qe7MMrD70XdasxlCDAtVrJv4vWP+sgadkDIsElzI9uN0gIdfYPNGlXOnFmMsljdFeLNAHmxorE/v33bv7oqalaK4/rWhL2k8DeThFEcaf1ZKJ0+HY4wegXOS5YAuI31zx5OJI8cib9L5UImpojgdS2QHfS0DFrFA4GKxrrEjs3/9fuy8e9DktdbFaQgz4AeUCvD2g5nOuO6VrJv0fDItL4E4JogjVsOUScm7BYMfEB93FC+b/oIuFTEiAcMYx+wsGahvDyVjnq4nmfReDUn1RH6pGKlayPBQW4hUYO0qamV0z8WpYMV0KhUH0tY4Q+ZfSG/AZnBUmTuDTiFsVNcFgbWPIjna8YLfun1H38uvHgVLdnvJN5rbdF9OJQrxbn5RJf1Yw1IDqzZVPfhEbiP5aN1BLUkG9F0MNBQt+UiBxj71rhdCaHno07QoLt1YIgGA7M9BbGC1zM5Bk+4GnwUIt3XNxw69cKixnPmyHNk121Yvcji9TrMIiEEtA6HAqDV4z8U5I6T+hzmyD3GW9Hx5vCDIGAzZzrIn5/eFwhxITVlVdKFBVF7Q7W59IdLSev/viprF7vt74S4JqEtsFlKWDijUpU6z8vo+eS0sZamyBlN7aBEONejXUsGCoYQDjvVvT8GNAZGaEXoqVUvTLH8VS/snCnmvVDQFa/tjR1sdsO3AdwHS/Wz0LgAptnTmJZkGZaq7ZysDyvsxCHxtwLdoxCiPMn2AgewQc2mb2eJMJWAQV0xHzlXZpkA4YWCiNQ5kdCtQ0WuCf4NmscwOmsd/f8/VBD2u3gDKduUmCmahTpm4rAytTrxQyLWW/uGUoKNejMNQ4CTr13cFF8GggenDgpVY89wLr6BHgsQ7DOjidTff5dbI0DnlhAEqSHdjNNh5fiw3Llu75+pC/IB0q2aqoAHcE3XkovZblp881yVJW+VKuPWDcKV36QqU0qJT+dPiN6AIXX7cGHPA4G7COGiF1AFaffDfQsw55KMsCoBok2bqfT7sTz7l+zzcGbdNHc+uVZzZh8jGZwOtzKAOrz12Wpxu8LgkWbr0XUvopEKimGWp4gEUJmAGZl2L1DVgsAYAKhAO19ZJs3o9j+Zkkg8v3XFL/rLaM9frwmbZulj6AppaBNYDOG/CtpApcAmJYuPUnMNS4SNr2gmO2HP/0TDdgMjEBRmDxkjLvOVEs3pWA92hQqDpJtuxrR7k/hl/AFS9f3PQirolAZCBvI9XURxP7/1MGVv/7Lj93upJqLWzh1qWQ0s/pcqdECT6umKGQxwAV05R5j9L8C8A6vMehUAFlBcNhq7oGgNp/wLKTt8bDVatevbDmNT6TIoOtg15MpvyFMTEPoQysPHTigIugTzG5Gowy9OoXPPEdMPTXwykwAUVqxi2KFUwKMgUWKRYYpWhchn/wGGCxAU5BkLWLeQegrATW8cJWVTUB9Q6YqlsqrI4f/P3iEW+zvpNus8Nb3wAVM6sEA25E9wLKwOreHyU8gycf405pwdavwZ3S7ZIAP5+EOyUbUvoUuFxQxeLY76FSjjj+WC+giK6kFa4IWxVVYMr3vQ5ErgzG965+ceZxyqErhXpjUsEAZTqwDCzTE36JzYxx/tZzYFS2DkpzdMHZiRcF7866L5DjFhJE7IhRx8CNfi135COg7EBFdQig4izvJSwdLU80t/7k1SuOamfTCk2h0ruvDKz0HvHDuRGkLtx2OpistXZlw1C7+T2w3xBWwV9yuKpKhh0xApuIYJ+gZDJgVdUGaMQAQD0HfN2w59Wmnxu5kwOoxaBQEfJbRQtlYBWtq/v4IJdyjbx9b1Pzu3uvxXB4QbCmqT4MnqmiBgYL4Mq4aZ7diQleLPokhKM37P5G453mKTrkzcCyi6OuY5KLFpeBVbSu7seDPLKuCffZw5rfjU6OJ2LjIQyFm2S7E2B7MRBI/OmlbwzdbEpXQPWyjmfyFjL+/4JPu45FLkyEAAAAAElFTkSuQmCC'); background-size: 24px; background-repeat: no-repeat; - background-position: left 50%; + background-position: left center; padding-left: 36px; font-size: 20px; letter-spacing: -0.04rem; @@ -42,7 +42,7 @@ body { .message { font-weight: 300; - font-size: 1.3rem; + font-size: 1.4rem; } body.error .message { diff --git a/extensions/vscode-account/package.json b/extensions/vscode-account/package.json index 1cc728195e9..ea3c2c2b891 100644 --- a/extensions/vscode-account/package.json +++ b/extensions/vscode-account/package.json @@ -1,8 +1,8 @@ { "name": "vscode-account", "publisher": "vscode", - "displayName": "Account", - "description": "", + "displayName": "%displayName%", + "description": "%description%", "version": "0.0.1", "engines": { "vscode": "^1.42.0" @@ -15,6 +15,33 @@ "*" ], "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "microsoft.signin", + "title": "%signIn%", + "category": "%displayName%" + }, + { + "command": "microsoft.signout", + "title": "%signOut%", + "category": "%displayName%" + } + ], + "configuration": { + "title": "Microsoft Account", + "properties": { + "microsoftAccount.logLevel": { + "type": "string", + "enum": [ + "info", + "trace" + ], + "default": "info" + } + } + } + }, "scripts": { "vscode:prepublish": "npm run compile", "compile": "gulp compile-extension:vscode-account", @@ -24,6 +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/package.nls.json b/extensions/vscode-account/package.nls.json new file mode 100644 index 00000000000..c0bb4c4a6a0 --- /dev/null +++ b/extensions/vscode-account/package.nls.json @@ -0,0 +1,6 @@ +{ + "displayName": "Microsoft Account", + "description": "Microsoft authentication provider", + "signIn": "Sign In", + "signOut": "Sign Out" +} diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index 503a599d48a..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'; @@ -21,6 +22,7 @@ interface IToken { accessToken?: string; // When unable to refresh due to network problems, the access token becomes undefined expiresIn?: string; // How long access token is valid, in seconds + expiresAt?: number; // UNIX epoch time at which token will expire refreshToken: string; accountName: string; @@ -53,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'; @@ -80,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); @@ -102,6 +104,7 @@ export class AzureActiveDirectoryService { await Promise.all(refreshes); } catch (e) { + Logger.info('Failed to initialize stored data'); await this.clearSessions(); } } @@ -128,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 { @@ -137,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. @@ -153,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); } })); @@ -161,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(); @@ -183,12 +195,35 @@ export class AzureActiveDirectoryService { private convertToSession(token: IToken): vscode.AuthenticationSession { return { id: token.sessionId, - accessToken: () => !token.accessToken ? Promise.reject('Unavailable due to network problems') : Promise.resolve(token.accessToken), + getAccessToken: () => this.resolveAccessToken(token), accountName: token.accountName, scopes: token.scope.split(' ') }; } + private async resolveAccessToken(token: IToken): Promise<string> { + if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) { + token.expiresAt + ? Logger.info(`Token available from cache, expires in ${token.expiresAt - Date.now()} milliseconds`) + : Logger.info('Token available from cache'); + return Promise.resolve(token.accessToken); + } + + try { + Logger.info('Token expired or unavailable, trying refresh'); + const refreshedToken = await this.refreshToken(token.refreshToken, token.scope, token.sessionId); + if (refreshedToken.accessToken) { + return refreshedToken.accessToken; + } else { + throw new Error(); + } + } catch (e) { + throw new Error('Unavailable due to network problems'); + } + + throw new Error('Unavailable due to network problems'); + } + private getTokenClaims(accessToken: string): ITokenClaims { try { return JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString()); @@ -252,9 +287,9 @@ export class AzureActiveDirectoryService { res.writeHead(302, { Location: '/' }); res.end(); } catch (err) { - Logger.error(err.message); res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` }); res.end(); + throw new Error(err.message); } } catch (e) { Logger.error(e.message); @@ -263,6 +298,7 @@ export class AzureActiveDirectoryService { if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') { await this.loginWithoutLocalServer(scope); } + throw new Error(e.message); } finally { setTimeout(() => { server.close(); @@ -272,8 +308,8 @@ export class AzureActiveDirectoryService { private getCallbackEnvironment(callbackUri: vscode.Uri): string { switch (callbackUri.authority) { - case 'online.visualstudio.com,': - return 'vso'; + case 'online.visualstudio.com': + return 'vso,'; case 'online-ppe.core.vsengsaas.visualstudio.com': return 'vsoppe,'; case 'online.dev.core.vsengsaas.visualstudio.com': @@ -316,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.'); } @@ -350,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); @@ -360,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))); @@ -369,15 +406,16 @@ 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 { expiresIn: json.expires_in, + expiresAt: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined, 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' }; } @@ -435,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({ @@ -460,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); @@ -503,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); } @@ -521,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; @@ -532,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 7d896b01d6a..83f4135b753 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -5,31 +5,71 @@ import * as vscode from 'vscode'; import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper'; +import * as nls from 'vscode-nls'; -export async function activate(_: vscode.ExtensionContext) { +const localize = nls.loadMessageBundle(); + +export const DEFAULT_SCOPES = 'https://management.core.windows.net/.default offline_access'; + +export async function activate(context: vscode.ExtensionContext) { const loginService = new AzureActiveDirectoryService(); await loginService.initialize(); - vscode.authentication.registerAuthenticationProvider({ - id: 'MSA', + context.subscriptions.push(vscode.authentication.registerAuthenticationProvider({ + id: 'microsoft', displayName: 'Microsoft', onDidChangeSessions: onDidChangeSessions.event, getSessions: () => Promise.resolve(loginService.sessions), 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) { - vscode.window.showErrorMessage(`Logging in failed: ${e}`); throw e; } }, logout: async (id: string) => { - return loginService.logout(id); + await loginService.logout(id); + onDidChangeSessions.fire({ added: [], removed: [id], changed: [] }); } - }); + })); + + context.subscriptions.push(vscode.commands.registerCommand('microsoft.signin', () => { + return loginService.login(DEFAULT_SCOPES); + })); + + context.subscriptions.push(vscode.commands.registerCommand('microsoft.signout', async () => { + const sessions = loginService.sessions; + if (sessions.length === 0) { + return; + } + + if (sessions.length === 1) { + 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; + } + + const selectedSession = await vscode.window.showQuickPick(sessions.map(session => { + return { + id: session.id, + label: session.accountName + }; + })); + + if (selectedSession) { + await loginService.logout(selectedSession.id); + onDidChangeSessions.fire({ added: [], removed: [selectedSession.id], changed: [] }); + vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out.")); + return; + } + })); return; } diff --git a/extensions/vscode-account/src/keychain.ts b/extensions/vscode-account/src/keychain.ts index 465a160fef7..76f8f7bceb2 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,32 @@ 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}`); + + // Temporary fix for #94005 + // This happens when processes write simulatenously to the keychain, most + // likely when trying to refresh the token. Ignore the error since additional + // writes after the first one do not matter. Should actually be fixed upstream. + if (e.message === 'The specified item already exists in the keychain.') { + return; + } + + 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 3acdda242e9..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,16 @@ 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" + integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== + 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/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 8ac6b2806ca..e72c27967f8 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -1,127 +1,123 @@ { - "name": "vscode-api-tests", - "description": "API tests 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": { - "configuration": { - "type": "object", - "title": "Test Config", - "properties": { - "farboo.config0": { - "type": "boolean", - "default": true - }, - "farboo.nested.config1": { - "type": "number", - "default": 42 - }, - "farboo.nested.config2": { - "type": "string", - "default": "Das Pferd frisst kein Reis." - }, - "farboo.config4": { - "type": "string" - }, - "farboo.get": { - "type": "string", - "default": "get-prop" - } - } - }, - "configurationDefaults": { - "[abcLang]": { - "editor.lineNumbers": "off", - "editor.tabSize": 2 - } - }, - "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", - "typescript": "^1.6.2", - "vscode": "1.1.5" - } + "name": "vscode-api-tests", + "description": "API tests for VS Code", + "version": "0.0.1", + "publisher": "vscode", + "license": "MIT", + "enableProposedApi": true, + "private": true, + "activationEvents": [], + "main": "./out/extension", + "engines": { + "vscode": "^1.25.0" + }, + "contributes": { + "configuration": { + "type": "object", + "title": "Test Config", + "properties": { + "farboo.config0": { + "type": "boolean", + "default": true + }, + "farboo.nested.config1": { + "type": "number", + "default": 42 + }, + "farboo.nested.config2": { + "type": "string", + "default": "Das Pferd frisst kein Reis." + }, + "farboo.config4": { + "type": "string" + }, + "farboo.get": { + "type": "string", + "default": "get-prop" + } + } + }, + "configurationDefaults": { + "[abcLang]": { + "editor.lineNumbers": "off", + "editor.tabSize": 2 + } + }, + "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", + "typescript": "^1.6.2", + "vscode": "1.1.5" + } } 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 84e323b789d..d8382f3b476 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 { @@ -409,8 +411,7 @@ suite('window namespace tests', () => { // const terminal = window.createTerminal({ name: 'foo', pty }); // }); - // https://github.com/microsoft/vscode/issues/90437 - test.skip('should respect dimension overrides', (done) => { + test('should respect dimension overrides', (done) => { disposables.push(window.onDidOpenTerminal(term => { try { equal(terminal, term); @@ -423,15 +424,18 @@ suite('window namespace tests', () => { // HACK: Ignore the event if dimension(s) are zero (#83778) return; } - try { - equal(e.dimensions.columns, 10); - equal(e.dimensions.rows, 5); - equal(e.terminal, terminal); - } catch (e) { - done(e); + // The default pty dimensions have a chance to appear here since override + // dimensions happens after the terminal is created. If so just ignore and + // wait for the right dimensions + if (e.dimensions.columns === 10 || e.dimensions.rows === 5) { + try { + equal(e.terminal, terminal); + } catch (e) { + done(e); + } + disposables.push(window.onDidCloseTerminal(() => done())); + terminal.dispose(); } - disposables.push(window.onDidCloseTerminal(() => done())); - terminal.dispose(); })); })); const writeEmitter = new EventEmitter<string>(); @@ -469,7 +473,7 @@ suite('window namespace tests', () => { const pty: Pseudoterminal = { onDidWrite: writeEmitter.event, onDidClose: closeEmitter.event, - open: () => closeEmitter.fire(), + open: () => closeEmitter.fire(undefined), close: () => { } }; const terminal = window.createTerminal({ name: 'foo', pty }); @@ -562,5 +566,201 @@ 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); + } + // 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); + } + // 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 e785f1d4afb..704d2f115ed 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -3,17 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'mocha'; import * as assert from 'assert'; -import * as vscode from 'vscode'; +import 'mocha'; +import * as os from 'os'; import { join } from 'path'; -import { closeAllEditors, disposeAll, conditionalTest } from '../utils'; +import * as vscode from 'vscode'; +import { closeAllEditors, conditionalTest, delay, disposeAll } from '../utils'; 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) { @@ -332,8 +333,30 @@ suite('Webview tests', () => { webview.webview.postMessage({ value: 1 }); await firstResponse; assert.strictEqual(webview.viewColumn, vscode.ViewColumn.One); - }); + + if (os.platform() === 'darwin') { + conditionalTest('webview can copy text from webview', async () => { + const expectedText = `webview text from: ${Date.now()}!`; + + const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); + const ready = getMesssage(webview); + + + webview.webview.html = createHtmlDocumentWithBody(/*html*/` + <b>${expectedText}</b> + <script> + const vscode = acquireVsCodeApi(); + document.execCommand('selectAll'); + vscode.postMessage({ type: 'ready' }); + </script>`); + await ready; + + await vscode.commands.executeCommand('editor.action.webvieweditor.copy'); + await delay(200); // Make sure copy has time to reach webview + assert.strictEqual(await vscode.env.clipboard.readText(), expectedText); + }); + } }); function createHtmlDocumentWithBody(body: string): string { 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 ce6274157a1..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[] = []; @@ -201,9 +201,29 @@ suite('workspace-event', () => { assert.equal(onDidRename?.files[0].newUri.toString(), newUri.toString()); }); - test('onWillRename - make changes', async function () { + test('onWillRename - make changes (saved file)', function () { + return testOnWillRename(false); + }); + + test('onWillRename - make changes (dirty file)', function () { + return testOnWillRename(true); + }); + + async function testOnWillRename(withDirtyFile: boolean): Promise<void> { const oldUri = await createRandomFile('BAR'); + + if (withDirtyFile) { + const edit = new vscode.WorkspaceEdit(); + edit.insert(oldUri, new vscode.Position(0, 0), 'BAR'); + + const success = await vscode.workspace.applyEdit(edit); + assert.ok(success); + + const oldDocument = await vscode.workspace.openTextDocument(oldUri); + assert.ok(oldDocument.isDirty); + } + const newUri = oldUri.with({ path: oldUri.path + '-NEW' }); const anotherFile = await createRandomFile('BAR'); @@ -229,7 +249,13 @@ suite('workspace-event', () => { assert.equal(onWillRename?.files[0].oldUri.toString(), oldUri.toString()); assert.equal(onWillRename?.files[0].newUri.toString(), newUri.toString()); - assert.equal((await vscode.workspace.openTextDocument(newUri)).getText(), 'FOOBAR'); - assert.equal((await vscode.workspace.openTextDocument(anotherFile)).getText(), 'FARBOO'); - }); + const newDocument = await vscode.workspace.openTextDocument(newUri); + const anotherDocument = await vscode.workspace.openTextDocument(anotherFile); + + assert.equal(newDocument.getText(), withDirtyFile ? 'FOOBARBAR' : 'FOOBAR'); + assert.equal(anotherDocument.getText(), 'FARBOO'); + + assert.ok(newDocument.isDirty); + assert.ok(anotherDocument.isDirty); + } }); 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 3e56f927168..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); @@ -215,13 +215,6 @@ suite('workspace-namespace', () => { }); test('eol, change via onWillSave', async function () { - if (vscode.env.uiKind === vscode.UIKind.Web) { - // TODO@Jo Test seems to fail when running in web due to - // onWillSaveTextDocument not getting called - this.skip(); - return; - } - let called = false; let sub = vscode.workspace.onWillSaveTextDocument(e => { called = true; 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 da71634c2e7..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.0" - }, - "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-colorize-tests/yarn.lock b/extensions/vscode-colorize-tests/yarn.lock index c6b3fdb4313..b8a66d65eff 100644 --- a/extensions/vscode-colorize-tests/yarn.lock +++ b/extensions/vscode-colorize-tests/yarn.lock @@ -1042,10 +1042,10 @@ json3@3.3.2: resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= -jsonc-parser@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" - integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== +jsonc-parser@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== jsonify@~0.0.0: version "0.0.0" 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/extensions/yarn.lock b/extensions/yarn.lock index 1d6447cfe6d..83bd84cdd90 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@^3.8.1-rc: - version "3.8.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.1-rc.tgz#f94333c14da70927ccd887be2e91be652a9a09f6" - integrity sha512-aOIe066DyZn2uYIiND6fXMUUJ70nxwu/lKhA92QuQzXyC86fr0ywo1qvO8l2m0EnDcfjprYPuFRgNgDj7U2GlQ== +typescript@3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== diff --git a/package.json b/package.json index e8d355e18cd..24b1473e266 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.43.0", - "distro": "a87981a5cc52fed24735d447e954adccb9c9f914", + "version": "1.45.0", + "distro": "d97879255c4f6590c5ccccfd1df6996018dfe0e5", "author": { "name": "Microsoft Corporation" }, @@ -40,27 +40,27 @@ "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", - "node-pty": "^0.10.0-beta2", + "node-pty": "0.10.0-beta7", "onigasm-umd": "2.2.5", "semver-umd": "^5.5.5", "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", "vscode-sqlite3": "4.0.9", "vscode-textmate": "4.4.0", - "xterm": "4.5.0-beta.4", - "xterm-addon-search": "0.5.0", - "xterm-addon-unicode11": "0.1.1", - "xterm-addon-web-links": "0.2.1", - "xterm-addon-webgl": "0.5.0", + "xterm": "4.5.0-beta.21", + "xterm-addon-search": "0.6.0-beta.2", + "xterm-addon-unicode11": "0.2.0-beta.2", + "xterm-addon-web-links": "0.3.0-beta.5", + "xterm-addon-webgl": "0.6.0-beta.3", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, @@ -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", @@ -93,13 +94,15 @@ "copy-webpack-plugin": "^4.5.2", "coveralls": "^2.11.11", "cson-parser": "^1.3.3", + "css-loader": "^3.2.0", "debounce": "^1.0.0", - "electron": "7.1.11", + "electron": "7.2.1", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", "fancy-log": "^1.3.3", "fast-plist": "0.1.2", + "file-loader": "^4.2.0", "glob": "^5.0.13", "gulp": "^4.0.0", "gulp-atom-electron": "^1.22.0", @@ -139,21 +142,22 @@ "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", "rimraf": "^2.2.8", "sinon": "^1.17.2", "source-map": "^0.4.4", + "style-loader": "^1.0.0", "ts-loader": "^4.4.2", - "typescript": "^3.8.1-rc", + "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.37.0", + "vscode-debugprotocol": "^1.40.0", "vscode-nls-dev": "^3.3.1", "webpack": "^4.16.5", "webpack-cli": "^3.3.8", diff --git a/product.json b/product.json index 759d7655333..d3efd56b387 100644 --- a/product.json +++ b/product.json @@ -22,5 +22,82 @@ "urlProtocol": "code-oss", "extensionAllowedProposedApi": [ "ms-vscode.references-view" + ], + "builtInExtensions": [ + { + "name": "ms-vscode.node-debug", + "version": "1.44.4", + "repo": "https://github.com/Microsoft/vscode-node-debug", + "metadata": { + "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } + }, + { + "name": "ms-vscode.node-debug2", + "version": "1.42.2", + "repo": "https://github.com/Microsoft/vscode-node-debug2", + "metadata": { + "id": "36d19e17-7569-4841-a001-947eb18602b2", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } + }, + { + "name": "ms-vscode.references-view", + "version": "0.0.50", + "repo": "https://github.com/Microsoft/vscode-reference-view", + "metadata": { + "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } + }, + { + "name": "ms-vscode.js-debug-companion", + "version": "1.0.0", + "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" + } + }, + { + "name": "ms-vscode.js-debug-nightly", + "version": "2020.4.817", + "repo": "https://github.com/Microsoft/vscode-js-debug", + "metadata": { + "id": "7acbb4ce-c85a-49d4-8d95-a8054406ae97", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } + } ] } diff --git a/remote/package.json b/remote/package.json index 7d3a3755dd3..21d6887ee87 100644 --- a/remote/package.json +++ b/remote/package.json @@ -10,21 +10,21 @@ "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", + "node-pty": "0.10.0-beta7", "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", "vscode-textmate": "4.4.0", - "xterm": "4.5.0-beta.4", - "xterm-addon-search": "0.5.0", - "xterm-addon-unicode11": "0.1.1", - "xterm-addon-web-links": "0.2.1", - "xterm-addon-webgl": "0.5.0", + "xterm": "4.5.0-beta.21", + "xterm-addon-search": "0.6.0-beta.2", + "xterm-addon-unicode11": "0.2.0-beta.2", + "xterm-addon-web-links": "0.3.0-beta.5", + "xterm-addon-webgl": "0.6.0-beta.3", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index ffcd7add280..376f8075c73 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -5,10 +5,10 @@ "onigasm-umd": "2.2.5", "semver-umd": "^5.5.5", "vscode-textmate": "4.4.0", - "xterm": "4.5.0-beta.4", - "xterm-addon-search": "0.5.0", - "xterm-addon-unicode11": "0.1.1", - "xterm-addon-web-links": "0.2.1", - "xterm-addon-webgl": "0.5.0" + "xterm": "4.5.0-beta.21", + "xterm-addon-search": "0.6.0-beta.2", + "xterm-addon-unicode11": "0.2.0-beta.2", + "xterm-addon-web-links": "0.3.0-beta.5", + "xterm-addon-webgl": "0.6.0-beta.3" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index c9f5c9ebb95..59220c9de2f 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -31,27 +31,27 @@ vscode-textmate@4.4.0: dependencies: oniguruma "^7.2.0" -xterm-addon-search@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.5.0.tgz#cd3a2f8056084c28e236d4e732da37682010bcc2" - integrity sha512-zLVqVTrg5w2nk9fRj3UuVKCPo/dmFe/cLf3EM9Is5Dm6cgOoXmeo9eq2KgD8A0gquAflTFTf0ya2NaFmShHwyg== +xterm-addon-search@0.6.0-beta.2: + version "0.6.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.6.0-beta.2.tgz#a9408e6c95ad4c47cebc147bfb359ce33f9ccb9f" + integrity sha512-kl7irLdfOdjgCRhlaruGQy2L35BhcOw3dlcogj8HNmHcm98/qF6fO19sOmv7UjZz1ic6sNxtQQw9Sm+MMkxt+A== -xterm-addon-unicode11@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.1.1.tgz#b209ef137db38096f68636af4ef4d0c0acba85ad" - integrity sha512-z6vJTL+dpNljwAYzYoyDjJP8A2XjZuEosl0sRa+FGRf3jEyEVWquDM53MfUd1ztVdAPQ839qR6eYK1BXV04Bhw== +xterm-addon-unicode11@0.2.0-beta.2: + version "0.2.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.2.tgz#2a13ba5b08fdb1005be241816c4e3302674db4af" + integrity sha512-Y047mnIWrAj65TpStdyPYoPeDTX4en+XX4Y90KuQB3cW2xIyZj25NSVV9BZdqzSb7gk9M6KBvIcm8chj7S2N8Q== -xterm-addon-web-links@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" - integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== +xterm-addon-web-links@0.3.0-beta.5: + version "0.3.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.3.0-beta.5.tgz#a489ee89f1e48569760742b20ac349cb61b421cd" + integrity sha512-M+NvTY03TY/yt95xjZFEBgwBThfsYy/RsuJTT4ydDaGeQAJEuZjV2O8nc8gmzAKGxYsgxx9br0A9RyLp5yqKKw== -xterm-addon-webgl@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0.tgz#c1031dc7599cce3509824643ab5f15361c928e3e" - integrity sha512-hQrvabKCnwXFaEZ+YtoJM9Pm0CIBXL5KSwoU+RiGStU3KYTAcqYP2GsH3dWdvKX6kTWhWLS81dtDsGkfbOciuA== +xterm-addon-webgl@0.6.0-beta.3: + version "0.6.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.6.0-beta.3.tgz#48abe4607659caaec3175c0105298bba5e34be27" + integrity sha512-2mhW/4Qv4i4KhEbtOAL4bc9FPGXON8XuM3vfKXT0EauXy/7ygtPu8IqrYNvNo0uJUoW6gOf0d5+/6kUMak2YYg== -xterm@4.5.0-beta.4: - version "4.5.0-beta.4" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.5.0-beta.4.tgz#701f05553b643236d3fcd8bb7f14045bd4537c92" - integrity sha512-Yv1Bf60LTLBMaig1rv033hPz8hQGXZN6VYW2oe/409t2NbJXPg5xZgf47qyaWFV7a5k1BFiwjayJCWaL2nYBew== +xterm@4.5.0-beta.21: + version "4.5.0-beta.21" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.5.0-beta.21.tgz#2265b8bb47c4e9ac68816f423d62f3f52df2bb88" + integrity sha512-np74QU68AwZckkWl5LncLk/HcWT/DUWO1XKJaCKqY/UWc9VlYarTJWSUqZrZiZ6zHJ7LgG9lSzCPSxYvq7Mq5Q== diff --git a/remote/yarn.lock b/remote/yarn.lock index d576786f982..6a905c590d1 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" @@ -266,10 +271,10 @@ node-addon-api@1.6.2: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217" integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA== -node-pty@^0.10.0-beta2: - version "0.10.0-beta2" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta2.tgz#6fd0d2fbbe881869e4e19795a05c557ac958da81" - integrity sha512-IU2lzlPUZ+gKG7pHJjzBHpnuwPTxWGgT3iyQicZfdL7dwLvP5cm00QxavAXCInBmRkOMhvM4aBSKvfzqQnCDBA== +node-pty@0.10.0-beta7: + version "0.10.0-beta7" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta7.tgz#7e383b2d1fe2f34509b57187f5a9a6ff90c46111" + integrity sha512-oC2VyIz9YaIvv6lWjAPZbUzmhLW1ouFmxOogNRNQrKeUzUi2yM/QRmybs+dW/Mhd3V89Yh61Ml0J5yuWiMIBbw== dependencies: nan "^2.14.0" @@ -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" @@ -413,30 +413,30 @@ vscode-windows-registry@1.0.2: resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a" integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA== -xterm-addon-search@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.5.0.tgz#cd3a2f8056084c28e236d4e732da37682010bcc2" - integrity sha512-zLVqVTrg5w2nk9fRj3UuVKCPo/dmFe/cLf3EM9Is5Dm6cgOoXmeo9eq2KgD8A0gquAflTFTf0ya2NaFmShHwyg== +xterm-addon-search@0.6.0-beta.2: + version "0.6.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.6.0-beta.2.tgz#a9408e6c95ad4c47cebc147bfb359ce33f9ccb9f" + integrity sha512-kl7irLdfOdjgCRhlaruGQy2L35BhcOw3dlcogj8HNmHcm98/qF6fO19sOmv7UjZz1ic6sNxtQQw9Sm+MMkxt+A== -xterm-addon-unicode11@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.1.1.tgz#b209ef137db38096f68636af4ef4d0c0acba85ad" - integrity sha512-z6vJTL+dpNljwAYzYoyDjJP8A2XjZuEosl0sRa+FGRf3jEyEVWquDM53MfUd1ztVdAPQ839qR6eYK1BXV04Bhw== +xterm-addon-unicode11@0.2.0-beta.2: + version "0.2.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.2.tgz#2a13ba5b08fdb1005be241816c4e3302674db4af" + integrity sha512-Y047mnIWrAj65TpStdyPYoPeDTX4en+XX4Y90KuQB3cW2xIyZj25NSVV9BZdqzSb7gk9M6KBvIcm8chj7S2N8Q== -xterm-addon-web-links@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" - integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== +xterm-addon-web-links@0.3.0-beta.5: + version "0.3.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.3.0-beta.5.tgz#a489ee89f1e48569760742b20ac349cb61b421cd" + integrity sha512-M+NvTY03TY/yt95xjZFEBgwBThfsYy/RsuJTT4ydDaGeQAJEuZjV2O8nc8gmzAKGxYsgxx9br0A9RyLp5yqKKw== -xterm-addon-webgl@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0.tgz#c1031dc7599cce3509824643ab5f15361c928e3e" - integrity sha512-hQrvabKCnwXFaEZ+YtoJM9Pm0CIBXL5KSwoU+RiGStU3KYTAcqYP2GsH3dWdvKX6kTWhWLS81dtDsGkfbOciuA== +xterm-addon-webgl@0.6.0-beta.3: + version "0.6.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.6.0-beta.3.tgz#48abe4607659caaec3175c0105298bba5e34be27" + integrity sha512-2mhW/4Qv4i4KhEbtOAL4bc9FPGXON8XuM3vfKXT0EauXy/7ygtPu8IqrYNvNo0uJUoW6gOf0d5+/6kUMak2YYg== -xterm@4.5.0-beta.4: - version "4.5.0-beta.4" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.5.0-beta.4.tgz#701f05553b643236d3fcd8bb7f14045bd4537c92" - integrity sha512-Yv1Bf60LTLBMaig1rv033hPz8hQGXZN6VYW2oe/409t2NbJXPg5xZgf47qyaWFV7a5k1BFiwjayJCWaL2nYBew== +xterm@4.5.0-beta.21: + version "4.5.0-beta.21" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.5.0-beta.21.tgz#2265b8bb47c4e9ac68816f423d62f3f52df2bb88" + integrity sha512-np74QU68AwZckkWl5LncLk/HcWT/DUWO1XKJaCKqY/UWc9VlYarTJWSUqZrZiZ6zHJ7LgG9lSzCPSxYvq7Mq5Q== yauzl@^2.9.2: version "2.10.0" diff --git a/resources/linux/snap/electron-launch b/resources/linux/snap/electron-launch index 2a1c4395187..9f4eb6a23b8 100755 --- a/resources/linux/snap/electron-launch +++ b/resources/linux/snap/electron-launch @@ -2,7 +2,7 @@ # On Fedora $SNAP is under /var and there is some magic to map it to /snap. # We need to handle that case and reset $SNAP -SNAP=$(echo $SNAP | sed -e "s|/var/lib/snapd||g") +SNAP=$(echo "$SNAP" | sed -e "s|/var/lib/snapd||g") if [ "$SNAP_ARCH" == "amd64" ]; then ARCH="x86_64-linux-gnu" @@ -14,21 +14,21 @@ else ARCH="$SNAP_ARCH-linux-gnu" fi -export XDG_CACHE_HOME=$SNAP_USER_COMMON/.cache -if [[ -d $SNAP_USER_DATA/.cache && ! -e $XDG_CACHE_HOME ]]; then +GDK_CACHE_DIR="$SNAP_USER_COMMON/.cache" +if [[ -d "$SNAP_USER_DATA/.cache" && ! -e "$GDK_CACHE_DIR" ]]; then # the .cache directory used to be stored under $SNAP_USER_DATA, migrate it - mv $SNAP_USER_DATA/.cache $SNAP_USER_COMMON/ + mv "$SNAP_USER_DATA/.cache" "$SNAP_USER_COMMON/" fi -mkdir -p $XDG_CACHE_HOME +[ ! -d "$GDK_CACHE_DIR" ] && mkdir -p "$GDK_CACHE_DIR" # Gdk-pixbuf loaders -export GDK_PIXBUF_MODULE_FILE=$XDG_CACHE_HOME/gdk-pixbuf-loaders.cache -export GDK_PIXBUF_MODULEDIR=$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/2.10.0/loaders -if [ -f $SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders ]; then - $SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders > $GDK_PIXBUF_MODULE_FILE +export GDK_PIXBUF_MODULE_FILE="$GDK_CACHE_DIR/gdk-pixbuf-loaders.cache" +export GDK_PIXBUF_MODULEDIR="$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/2.10.0/loaders" +if [ -f "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" ]; then + "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" > "$GDK_PIXBUF_MODULE_FILE" fi # Create $XDG_RUNTIME_DIR if not exists (to be removed when https://pad.lv/1656340 is fixed) -[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p $XDG_RUNTIME_DIR -m 700 +[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p "$XDG_RUNTIME_DIR" -m 700 exec "$@" 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 4ba1a00b9f7..3bc09f54f4e 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -50,11 +50,14 @@ function code() { export VSCODE_LOGS= # Launch Code - exec "$CODE" . "$@" + exec "$CODE" . --no-sandbox "$@" } 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 0336f9eb700..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) { @@ -212,7 +208,7 @@ function onUnexpectedError(error, enableDeveloperTools) { console.error('[uncaught exception]: ' + error); - if (error.stack) { + if (error && error.stack) { console.error(error.stack); } } 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 0e90f4504c8..6506ad9817c 100644 --- a/src/main.js +++ b/src/main.js @@ -57,6 +57,12 @@ const nodeCachedDataDir = getNodeCachedDir(); // Configure static command line arguments const argvConfig = configureCommandlineSwitchesSync(args); +// Remove env set by snap https://github.com/microsoft/vscode/issues/85344 +if (process.env['SNAP']) { + delete process.env['GDK_PIXBUF_MODULE_FILE']; + delete process.env['GDK_PIXBUF_MODULEDIR']; +} + /** * Support user defined locale: load it early before app('ready') * to have more things running in parallel. @@ -82,7 +88,7 @@ app.once('ready', function () { traceOptions: args['trace-options'] || 'record-until-full,enable-sampling' }; - contentTracing.startRecording(traceOptions, () => onReady()); + contentTracing.startRecording(traceOptions).finally(() => onReady()); } else { onReady(); } @@ -131,8 +137,12 @@ function configureCommandlineSwitchesSync(cliArgs) { 'disable-hardware-acceleration', // provided by Electron - 'disable-color-correct-rendering' + 'disable-color-correct-rendering', + + // override for the color profile to use + 'force-color-profile' ]; + if (process.platform === 'linux') { SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); } @@ -147,7 +157,16 @@ function configureCommandlineSwitchesSync(cliArgs) { } const argvValue = argvConfig[argvKey]; - if (argvValue === true || argvValue === 'true') { + + // Color profile + if (argvKey === 'force-color-profile') { + if (argvValue) { + app.commandLine.appendSwitch(argvKey, argvValue); + } + } + + // Others + else if (argvValue === true || argvValue === 'true') { if (argvKey === 'disable-hardware-acceleration') { app.disableHardwareAcceleration(); // needs to be called explicitly } else { @@ -162,6 +181,9 @@ function configureCommandlineSwitchesSync(cliArgs) { app.commandLine.appendSwitch('js-flags', jsFlags); } + // TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873 + app.commandLine.appendSwitch('disable-features', 'LayoutNG'); + return argvConfig; } @@ -300,7 +322,7 @@ function getUserDataPath(cliArgs) { * @returns {ParsedArgs} */ function parseCLIArgs() { - const minimist = require('vscode-minimist'); + const minimist = require('minimist'); return minimist(process.argv, { string: [ @@ -328,7 +350,7 @@ function setCurrentWorkingDirectory() { function registerListeners() { /** - * Mac: when someone drops a file to the not-yet running VSCode, the open-file event fires even before + * macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before * the app-ready event. We listen very early for open-file and remember this upon startup as path to open. * * @type {string[]} @@ -340,7 +362,7 @@ function registerListeners() { }); /** - * React to open-url requests. + * macOS: react to open-url requests. * * @type {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 44595cf5246..19165d97b72 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -2,21 +2,24 @@ "compilerOptions": { "module": "amd", "moduleResolution": "node", - "noImplicitAny": true, "experimentalDecorators": true, "noImplicitReturns": true, "noUnusedLocals": true, - "noImplicitThis": true, - "alwaysStrict": true, - "strictBindCallApply": true, - "strictNullChecks": true, - "strictPropertyInitialization": true, + "strict": true, "forceConsistentCasingInFileNames": true, "baseUrl": ".", "paths": { "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..3f5391410a7 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); @@ -55,6 +51,8 @@ export const BrowserFeatures = { return KeyboardSupport.None; })(), - touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0, - pointerEvents: window.PointerEvent && ('ontouchstart' in window || window.navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0) + // 'ontouchstart' in window always evaluates to true with typescript's modern typings. This causes `window` to be + // `never` later in `window.navigator`. That's why we need the explicit `window as Window` cast + touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || (window as Window).navigator.msMaxTouchPoints > 0, + pointerEvents: window.PointerEvent && ('ontouchstart' in window || (window as Window).navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0 || (window as Window).navigator.msMaxTouchPoints > 0) }; 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/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts index 28ce15064f9..a5f9c18b2d6 100644 --- a/src/vs/base/browser/fastDomNode.ts +++ b/src/vs/base/browser/fastDomNode.ts @@ -244,11 +244,11 @@ export class FastDomNode { this.domNode.removeAttribute(name); } - public appendChild(child: FastDomNode): void { + public appendChild(child: FastDomNode): void { this.domNode.appendChild(child.domNode); } - public removeChild(child: FastDomNode): void { + public removeChild(child: FastDomNode): void { this.domNode.removeChild(child.domNode); } } 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/iframe.ts b/src/vs/base/browser/iframe.ts index 7868cafbba2..ade89e96ccc 100644 --- a/src/vs/base/browser/iframe.ts +++ b/src/vs/base/browser/iframe.ts @@ -98,7 +98,7 @@ export class IframeUtils { /** * Returns the position of `childWindow` relative to `ancestorWindow` */ - public static getPositionOfChildWindowRelativeToAncestorWindow(childWindow: Window, ancestorWindow: any) { + public static getPositionOfChildWindowRelativeToAncestorWindow(childWindow: Window, ancestorWindow: Window | null) { if (!ancestorWindow || childWindow === ancestorWindow) { 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 2918133f5e5..b40e1c1e8cd 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -25,7 +25,7 @@ export interface MarkdownRenderOptions extends FormattedTextRenderOptions { /** * Create html nodes for the given content element. */ -export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}): HTMLElement { +export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: marked.MarkedOptions = {}): HTMLElement { const element = createElement(options); const _uriMassage = function (part: string): string { @@ -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 @@ -169,10 +173,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende })); } - const markedOptions: marked.MarkedOptions = { - sanitize: true, - renderer - }; + markedOptions.sanitize = true; + markedOptions.renderer = renderer; const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource]; if (markdown.isTrusted) { diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 9956bc9112b..7166c4ec3a9 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -90,9 +90,9 @@ export class Gesture extends Disposable { this.targets = []; this.ignoreTargets = []; this._lastSetTapCountTime = 0; - this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e))); + this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e), { passive: false })); this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e))); - this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e))); + this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e), { passive: false })); } public static addTarget(element: HTMLElement): IDisposable { @@ -131,7 +131,9 @@ export class Gesture extends Disposable { @memoize private static isTouchDevice(): boolean { - return 'ontouchstart' in window as any || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0; + // `'ontouchstart' in window` always evaluates to true with typescript's modern typings. This causes `window` to be + // `never` later in `window.navigator`. That's why we need the explicit `window as Window` cast + return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || (window as Window).navigator.msMaxTouchPoints > 0; } public dispose(): void { @@ -247,7 +249,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 28e9c4b55ed..d277b9d2fca 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -104,7 +104,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { return this._action.enabled; } - setActionContext(newContext: any): void { + setActionContext(newContext: unknown): void { this._context = newContext; } @@ -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 @@ -248,7 +260,7 @@ export class ActionViewItem extends BaseActionViewItem { private cssClass?: string; - constructor(context: any, action: IAction, options: IActionViewItemOptions = {}) { + constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) { super(context, action, options); this.options = options; @@ -423,7 +435,7 @@ export class ActionBar extends Disposable implements IActionRunner { options: IActionBarOptions; private _actionRunner: IActionRunner; - private _context: any; + private _context: unknown; // View Items viewItems: IActionViewItem[]; @@ -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,15 +824,7 @@ 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?: any): Promise { + run(action: IAction, context?: unknown): Promise { return this._actionRunner.run(action, context); } @@ -838,7 +841,7 @@ export class ActionBar extends Disposable implements IActionRunner { export class SelectActionViewItem extends BaseActionViewItem { protected selectBox: SelectBox; - constructor(ctx: any, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) { + constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) { super(ctx, action); this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions); @@ -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/aria/aria.ts b/src/vs/base/browser/ui/aria/aria.ts index fc71827eafa..2867c1256dc 100644 --- a/src/vs/base/browser/ui/aria/aria.ts +++ b/src/vs/base/browser/ui/aria/aria.ts @@ -23,7 +23,8 @@ export function setARIAContainer(parent: HTMLElement) { statusContainer = document.createElement('div'); statusContainer.className = 'monaco-status'; - statusContainer.setAttribute('role', 'status'); + statusContainer.setAttribute('role', 'complementary'); + statusContainer.setAttribute('aria-live', 'polite'); statusContainer.setAttribute('aria-atomic', 'true'); ariaContainer.appendChild(statusContainer); @@ -80,4 +81,4 @@ function insertMessage(target: HTMLElement, msg: string, disableRepeat?: boolean // See https://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/ target.style.visibility = 'hidden'; target.style.visibility = 'visible'; -} \ No newline at end of file +} 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 2fa65d71923..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?d0510f6ecacbb2788db2b3162273a3d8") format("truetype"); + src: url("./codicon.ttf?a76e99e42eab7c1a55601640b708d820") format("truetype"); } .codicon[class*='codicon-'] { @@ -303,6 +303,7 @@ .codicon-paintcan:before { content: "\eb2a" } .codicon-pin:before { content: "\eb2b" } .codicon-play:before { content: "\eb2c" } +.codicon-run:before { content: "\eb2c" } .codicon-plug:before { content: "\eb2d" } .codicon-preserve-case:before { content: "\eb2e" } .codicon-preview:before { content: "\eb2f" } @@ -359,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" } @@ -413,5 +416,12 @@ .codicon-feedback:before { content: "\eb96" } .codicon-group-by-ref-type:before { content: "\eb97" } .codicon-ungroup-by-ref-type:before { content: "\eb98" } +.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 f712f5cd053..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 1249a329acf..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); @@ -93,7 +93,6 @@ .monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message-detail { line-height: 22px; flex: 1; /* let the message always grow */ - opacity: .9; } .monaco-dialog-box .dialog-message-row .dialog-message-container .dialog-message a:focus { diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 46190302d98..97bcd36f292 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -267,7 +267,13 @@ export class Dialog extends Disposable { if (this.checkbox) { this.checkbox.style(style); } + + if (this.messageDetailElement) { + const messageDetailColor = Color.fromHex(fgColor).transparent(.9); + this.messageDetailElement.style.color = messageDetailColor.makeOpaque(Color.fromHex(bgColor)).toString(); + } } + } } 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..39e4b80cbcd 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'; } @@ -561,7 +560,7 @@ export class InputBox extends Widget { this.element.style.backgroundColor = background; this.element.style.color = foreground; - this.input.style.backgroundColor = background; + this.input.style.backgroundColor = 'inherit'; this.input.style.color = foreground; this.element.style.borderWidth = border ? '1px' : ''; diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index f07e22e1bee..25a09d22fae 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -63,14 +63,6 @@ export interface IIdentityProvider { getId(element: T): { toString(): string; }; } -export enum ListAriaRootRole { - /** default tree structure role */ - TREE = 'tree', - - /** role='tree' can interfere with screenreaders reading nested elements inside the tree row. Use FORM in that case. */ - FORM = 'form' -} - export interface IKeyboardNavigationLabelProvider { /** diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 352dd6b8e34..d980e915cb5 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -7,10 +7,11 @@ import 'vs/css!./list'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent } from './list'; -import { List, IListStyles, IListOptions } from './listWidget'; +import { List, IListStyles, IListOptions, IListAccessibilityProvider } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import { Event } from 'vs/base/common/event'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; export interface IPagedRenderer extends IListRenderer { renderPlaceholder(index: number, templateData: TTemplateData): void; @@ -70,6 +71,50 @@ class PagedRenderer implements IListRenderer implements IListAccessibilityProvider { + + constructor( + private modelProvider: () => IPagedModel, + private accessibilityProvider: IListAccessibilityProvider + ) { } + + getAriaLabel(index: number): string | null { + const model = this.modelProvider(); + + if (!model.isResolved(index)) { + return null; + } + + return this.accessibilityProvider.getAriaLabel(model.get(index)); + } +} + +export interface IPagedListOptions { + readonly enableKeyboardNavigation?: boolean; + readonly automaticKeyboardNavigation?: boolean; + readonly ariaLabel?: string; + readonly keyboardSupport?: boolean; + readonly multipleSelectionSupport?: boolean; + readonly accessibilityProvider?: IListAccessibilityProvider; + + // list view options + readonly useShadows?: boolean; + readonly verticalScrollMode?: ScrollbarVisibility; + readonly setRowLineHeight?: boolean; + readonly setRowHeight?: boolean; + readonly supportDynamicHeights?: boolean; + readonly mouseSupport?: boolean; + readonly horizontalScrolling?: boolean; + readonly additionalScrollHeight?: number; +} + +function fromPagedListOptions(modelProvider: () => IPagedModel, options: IPagedListOptions): IListOptions { + return { + ...options, + accessibilityProvider: options.accessibilityProvider && new PagedAccessibilityProvider(modelProvider, options.accessibilityProvider) + }; +} + export class PagedList implements IDisposable { private list: List; @@ -80,10 +125,11 @@ export class PagedList implements IDisposable { container: HTMLElement, virtualDelegate: IListVirtualDelegate, renderers: IPagedRenderer[], - options: IListOptions = {} + options: IPagedListOptions = {} ) { - const pagedRenderers = renderers.map(r => new PagedRenderer>(r, () => this.model)); - this.list = new List(user, container, virtualDelegate, pagedRenderers, options); + const modelProvider = () => this.model; + const pagedRenderers = renderers.map(r => new PagedRenderer>(r, modelProvider)); + this.list = new List(user, container, virtualDelegate, pagedRenderers, fromPagedListOptions(modelProvider, options)); } getHTMLElement(): HTMLElement { @@ -114,16 +160,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 +237,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 72fd9a63ad3..cdda528ce46 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -21,6 +21,7 @@ import { equals, distinct } from 'vs/base/common/arrays'; import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd'; import { disposableTimeout, Delayer } from 'vs/base/common/async'; import { isFirefox } from 'vs/base/browser/browser'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; interface IItem { readonly id: string; @@ -40,11 +41,11 @@ export interface IListViewDragAndDrop extends IListDragAndDrop { getDragElements(element: T): T[]; } -export interface IAriaProvider { - getSetSize(element: T, index: number, listLength: number): number; - getPosInSet(element: T, index: number): number; +export interface IListViewAccessibilityProvider { + getSetSize?(element: T, index: number, listLength: number): number; + getPosInSet?(element: T, index: number): number; getRole?(element: T): string; - isChecked?(element: T): boolean; + isChecked?(element: T): boolean | undefined; } export interface IListViewOptions { @@ -52,10 +53,11 @@ export interface IListViewOptions { readonly useShadows?: boolean; readonly verticalScrollMode?: ScrollbarVisibility; readonly setRowLineHeight?: boolean; + readonly setRowHeight?: boolean; readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; - readonly ariaProvider?: IAriaProvider; + readonly accessibilityProvider?: IListViewAccessibilityProvider; readonly additionalScrollHeight?: number; } @@ -63,6 +65,7 @@ const DefaultOptions = { useShadows: true, verticalScrollMode: ScrollbarVisibility.Auto, setRowLineHeight: true, + setRowHeight: true, supportDynamicHeights: false, dnd: { getDragElements(e: T) { return [e]; }, @@ -149,6 +152,40 @@ function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): return f1 === f2; } +class ListViewAccessibilityProvider implements Required> { + + readonly getSetSize: (element: any, index: number, listLength: number) => number; + readonly getPosInSet: (element: any, index: number) => number; + readonly getRole: (element: T) => string; + readonly isChecked: (element: T) => boolean | undefined; + + constructor(accessibilityProvider?: IListViewAccessibilityProvider) { + if (accessibilityProvider?.getSetSize) { + this.getSetSize = accessibilityProvider.getSetSize.bind(accessibilityProvider); + } else { + this.getSetSize = (e, i, l) => l; + } + + if (accessibilityProvider?.getPosInSet) { + this.getPosInSet = accessibilityProvider.getPosInSet.bind(accessibilityProvider); + } else { + this.getPosInSet = (e, i) => i + 1; + } + + if (accessibilityProvider?.getRole) { + this.getRole = accessibilityProvider.getRole.bind(accessibilityProvider); + } else { + this.getRole = _ => 'listitem'; + } + + if (accessibilityProvider?.isChecked) { + this.isChecked = accessibilityProvider.isChecked.bind(accessibilityProvider); + } else { + this.isChecked = _ => undefined; + } + } +} + export class ListView implements ISpliceable, IDisposable { private static InstanceCount = 0; @@ -174,10 +211,11 @@ export class ListView implements ISpliceable, IDisposable { private dragOverAnimationStopDisposable: IDisposable = Disposable.None; private dragOverMouseY: number = 0; private setRowLineHeight: boolean; + private setRowHeight: boolean; private supportDynamicHeights: boolean; private horizontalScrolling: boolean; private additionalScrollHeight: number; - private ariaProvider: IAriaProvider; + private accessibilityProvider: ListViewAccessibilityProvider; private scrollWidth: number | undefined; private dnd: IListViewDragAndDrop; @@ -194,6 +232,8 @@ export class ListView implements ISpliceable, IDisposable { get contentHeight(): number { return this.rangeMap.size; } get onDidScroll(): Event { return this.scrollableElement.onScroll; } + get onWillScroll(): Event { return this.scrollableElement.onWillScroll; } + get containerDomNode(): HTMLElement { return this.rowsContainer; } constructor( container: HTMLElement, @@ -231,7 +271,7 @@ export class ListView implements ISpliceable, IDisposable { this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight; - this.ariaProvider = options.ariaProvider || { getSetSize: (e, i, length) => length, getPosInSet: (_, index) => index + 1 }; + this.accessibilityProvider = new ListViewAccessibilityProvider(options.accessibilityProvider); this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; @@ -262,12 +302,58 @@ export class ListView implements ISpliceable, IDisposable { domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables); this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); + this.setRowHeight = getOrDefault(options, o => o.setRowHeight, DefaultOptions.setRowHeight); this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights); this.dnd = getOrDefault, IListViewDragAndDrop>(options, o => o.dnd, DefaultOptions.dnd); this.layout(); } + updateOptions(options: IListViewOptions) { + if (options.additionalScrollHeight !== undefined) { + this.additionalScrollHeight = options.additionalScrollHeight; + } + } + + triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent); + } + + updateElementHeight(index: number, size: number, anchorIndex: number | null): void { + if (this.items[index].size === size) { + return; + } + + const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + + 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, Math.max(0, this.lastRenderTop + heightDiff), this.lastRenderHeight, undefined, undefined, true); + + this.eventuallyUpdateScrollDimensions(); + + if (this.supportDynamicHeights) { + this._rerender(this.lastRenderTop, this.lastRenderHeight); + } + return; + } + splice(start: number, deleteCount: number, elements: T[] = []): T[] { if (this.splicing) { throw new Error('Can\'t run recursive splices.'); @@ -511,14 +597,21 @@ export class ListView implements ISpliceable, IDisposable { // Render - private render(renderTop: number, renderHeight: number, renderLeft: number, scrollWidth: number): void { - const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + private render(previousRenderRange: IRange, renderTop: number, renderHeight: number, renderLeft: number | undefined, scrollWidth: number | undefined, updateItemsInDOM: boolean = false): void { const renderRange = this.getRenderRange(renderTop, renderHeight); const rangesToInsert = Range.relativeComplement(renderRange, previousRenderRange); const rangesToRemove = Range.relativeComplement(previousRenderRange, renderRange); const beforeElement = this.getNextToLastElement(rangesToInsert); + if (updateItemsInDOM) { + const rangesToUpdate = Range.intersect(previousRenderRange, renderRange); + + for (let i = rangesToUpdate.start; i < rangesToUpdate.end; i++) { + this.updateItemInDOM(this.items[i], i); + } + } + for (const range of rangesToInsert) { for (let i = range.start; i < range.end; i++) { this.insertItemInDOM(i, beforeElement); @@ -531,10 +624,13 @@ export class ListView implements ISpliceable, IDisposable { } } - this.rowsContainer.style.left = `-${renderLeft}px`; + if (renderLeft !== undefined) { + this.rowsContainer.style.left = `-${renderLeft}px`; + } + this.rowsContainer.style.top = `-${renderTop}px`; - if (this.horizontalScrolling) { + if (this.horizontalScrolling && scrollWidth !== undefined) { this.rowsContainer.style.width = `${Math.max(scrollWidth, this.renderWidth)}px`; } @@ -549,11 +645,11 @@ export class ListView implements ISpliceable, IDisposable { if (!item.row) { item.row = this.cache.alloc(item.templateId); - const role = this.ariaProvider.getRole ? this.ariaProvider.getRole(item.element) : 'treeitem'; + const role = this.accessibilityProvider.getRole(item.element); item.row!.domNode!.setAttribute('role', role); - const checked = this.ariaProvider.isChecked ? this.ariaProvider.isChecked(item.element) : undefined; + const checked = this.accessibilityProvider.isChecked(item.element); if (typeof checked !== 'undefined') { - item.row!.domNode!.setAttribute('aria-checked', String(checked)); + item.row!.domNode!.setAttribute('aria-checked', String(!!checked)); } } @@ -614,7 +710,10 @@ export class ListView implements ISpliceable, IDisposable { private updateItemInDOM(item: IItem, index: number): void { item.row!.domNode!.style.top = `${this.elementTop(index)}px`; - item.row!.domNode!.style.height = `${item.size}px`; + + if (this.setRowHeight) { + item.row!.domNode!.style.height = `${item.size}px`; + } if (this.setRowLineHeight) { item.row!.domNode!.style.lineHeight = `${item.size}px`; @@ -622,8 +721,8 @@ export class ListView implements ISpliceable, IDisposable { item.row!.domNode!.setAttribute('data-index', `${index}`); item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false'); - item.row!.domNode!.setAttribute('aria-setsize', String(this.ariaProvider.getSetSize(item.element, index, this.length))); - item.row!.domNode!.setAttribute('aria-posinset', String(this.ariaProvider.getPosInSet(item.element, index))); + item.row!.domNode!.setAttribute('aria-setsize', String(this.accessibilityProvider.getSetSize(item.element, index, this.length))); + item.row!.domNode!.setAttribute('aria-posinset', String(this.accessibilityProvider.getPosInSet(item.element, index))); item.row!.domNode!.setAttribute('id', this.getElementDomId(index)); DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget); @@ -733,7 +832,8 @@ export class ListView implements ISpliceable, IDisposable { private onScroll(e: ScrollEvent): void { try { - this.render(e.scrollTop, e.height, e.scrollLeft, e.scrollWidth); + const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + this.render(previousRenderRange, e.scrollTop, e.height, e.scrollLeft, e.scrollWidth); if (this.supportDynamicHeights) { this._rerender(e.scrollTop, e.height); @@ -975,9 +1075,10 @@ export class ListView implements ISpliceable, IDisposable { // Util private getItemIndexFromEventTarget(target: EventTarget | null): number | undefined { + const scrollableElement = this.scrollableElement.getDomNode(); let element: HTMLElement | null = target as (HTMLElement | null); - while (element instanceof HTMLElement && element !== this.rowsContainer) { + while (element instanceof HTMLElement && element !== this.rowsContainer && scrollableElement.contains(element)) { const rawIndex = element.getAttribute('data-index'); if (rawIndex) { @@ -1088,7 +1189,19 @@ export class ListView implements ISpliceable, IDisposable { return 0; } + if (!!this.virtualDelegate.hasDynamicHeight && !this.virtualDelegate.hasDynamicHeight(item.element)) { + return 0; + } + const size = item.size; + + if (!this.setRowHeight && item.row && item.row.domNode) { + let newSize = item.row.domNode.offsetHeight; + item.size = newSize; + item.lastDynamicHeightWidth = this.renderWidth; + return newSize - size; + } + const row = this.cache.alloc(item.templateId); row.domNode!.style.height = ''; diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 026344c5e1b..0e7bac0e657 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -16,8 +16,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole, ListError, IKeyboardNavigationDelegate } from './list'; -import { ListView, IListViewOptions, IListViewDragAndDrop, IAriaProvider } from './listView'; +import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListError, IKeyboardNavigationDelegate } from './list'; +import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; @@ -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'); + } } } } @@ -684,25 +686,9 @@ export interface IStyleController { style(styles: IListStyles): 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/#aria-label - */ +export interface IListAccessibilityProvider extends IListViewAccessibilityProvider { getAriaLabel(element: T): string | null; - - /** - * https://www.w3.org/TR/wai-aria/#aria-level - */ getAriaLevel?(element: T): number | undefined; - onDidChangeActiveDescendant?: Event; getActiveDescendantId?(element: T): string | undefined; } @@ -834,23 +820,24 @@ export interface IListOptions { readonly automaticKeyboardNavigation?: boolean; readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider; readonly keyboardNavigationDelegate?: IKeyboardNavigationDelegate; - readonly ariaRole?: ListAriaRootRole | string; + readonly ariaRole?: string; readonly ariaLabel?: string; readonly keyboardSupport?: boolean; readonly multipleSelectionSupport?: boolean; readonly multipleSelectionController?: IMultipleSelectionController; readonly openController?: IOpenController; readonly styleController?: (suffix: string) => IStyleController; - readonly accessibilityProvider?: IAccessibilityProvider; + readonly accessibilityProvider?: IListAccessibilityProvider; // list view options readonly useShadows?: boolean; readonly verticalScrollMode?: ScrollbarVisibility; readonly setRowLineHeight?: boolean; + readonly setRowHeight?: boolean; readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; - readonly ariaProvider?: IAriaProvider; + readonly additionalScrollHeight?: number; } export interface IListStyles { @@ -890,7 +877,7 @@ const defaultStyles: IListStyles = { treeIndentGuidesStroke: Color.fromHex('#a9a9a9') }; -const DefaultOptions = { +const DefaultOptions: IListOptions = { keyboardSupport: true, mouseSupport: true, multipleSelectionSupport: true, @@ -899,8 +886,7 @@ const DefaultOptions = { onDragStart(): void { }, onDragOver() { return false; }, drop() { } - }, - ariaRootRole: ListAriaRootRole.TREE + } }; // TODO@Joao: move these utils into a SortedArray class @@ -1032,7 +1018,7 @@ class AccessibiltyRenderer implements IListRenderer { templateId: string = 'a18n'; - constructor(private accessibilityProvider: IAccessibilityProvider) { } + constructor(private accessibilityProvider: IListAccessibilityProvider) { } renderTemplate(container: HTMLElement): HTMLElement { return container; @@ -1107,6 +1093,7 @@ class ListViewDragAndDrop implements IListViewDragAndDrop { export interface IListOptionsUpdate { readonly enableKeyboardNavigation?: boolean; readonly automaticKeyboardNavigation?: boolean; + readonly additionalScrollHeight?: number; } export class List implements ISpliceable, IDisposable { @@ -1114,19 +1101,19 @@ export class List implements ISpliceable, IDisposable { private focus: Trait; private selection: Trait; private eventBufferer = new EventBufferer(); - private view: ListView; + protected view: ListView; private spliceable: ISpliceable; private styleController: IStyleController; private typeLabelController?: TypeLabelController; - private accessibilityProvider?: IAccessibilityProvider; + private accessibilityProvider?: IListAccessibilityProvider; 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)); } @@ -1197,8 +1184,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); @@ -1222,12 +1209,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); - } else { - this.view.domNode.setAttribute('role', _options.ariaRole); - } + this.view.domNode.setAttribute('role', _options.ariaRole ?? 'list'); if (_options.styleController) { this.styleController = _options.styleController(this.view.domId); @@ -1265,12 +1247,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 { @@ -1283,6 +1268,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 { @@ -1309,6 +1298,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(); } @@ -1493,9 +1486,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 c9a778aefb3..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'; @@ -167,6 +167,9 @@ export abstract class AbstractScrollableElement extends Widget { private readonly _onScroll = this._register(new Emitter()); public readonly onScroll: Event = this._onScroll.event; + private readonly _onWillScroll = this._register(new Emitter()); + public readonly onWillScroll: Event = this._onWillScroll.event; + protected constructor(element: HTMLElement, options: ScrollableElementCreationOptions, scrollable: Scrollable) { super(); element.style.overflow = 'hidden'; @@ -174,6 +177,7 @@ export abstract class AbstractScrollableElement extends Widget { this._scrollable = scrollable; this._register(this._scrollable.onScroll((e) => { + this._onWillScroll.fire(e); this._onDidScroll(e); this._onScroll.fire(e); })); @@ -283,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(); @@ -299,6 +313,10 @@ export abstract class AbstractScrollableElement extends Widget { this._revealOnScroll = value; } + public triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + this._onMouseWheel(new StandardWheelEvent(browserEvent)); + } + // -------------------- mouse wheel scrolling -------------------- private _setListeningToMouseWheel(shouldListen: boolean): void { @@ -318,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/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 846e156dfa3..8a7a9705228 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -86,7 +86,7 @@ export class ToolBar extends Disposable { return this.actionBar.actionRunner; } - set context(context: any) { + set context(context: unknown) { this.actionBar.context = context; if (this.toggleMenuActionViewItem.value) { this.toggleMenuActionViewItem.value.setActionContext(context); @@ -166,10 +166,8 @@ class ToggleMenuAction extends Action { this.toggleDropdownMenu = toggleDropdownMenu; } - run(): Promise { + async run(): Promise { this.toggleDropdownMenu(); - - return Promise.resolve(true); } get menuActions(): ReadonlyArray { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 9e584efcc56..7eed596d098 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -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'; @@ -162,6 +162,23 @@ function asListOptions(modelProvider: () => ITreeModel { + return options.accessibilityProvider!.isChecked!(node.element); + } : undefined, + getRole: options.accessibilityProvider && options.accessibilityProvider.getRole ? (node) => { + return options.accessibilityProvider!.getRole!(node.element); + } : () => 'treeitem', getAriaLabel(e) { return options.accessibilityProvider!.getAriaLabel(e.element); }, @@ -179,25 +196,7 @@ function asListOptions(modelProvider: () => ITreeModel { - return options.ariaProvider!.isChecked!(node.element); - } : undefined, - getRole: options.ariaProvider && options.ariaProvider.getRole ? (node) => { - return options.ariaProvider!.getRole!(node.element); - } : undefined - } + ariaRole: 'tree' }; } @@ -1320,7 +1319,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 6ddb3fe5cb8..d921bb20459 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -11,7 +11,7 @@ import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle' import { Emitter, Event } from 'vs/base/common/event'; import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; -import { Iterator } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { IDragAndDropData } from 'vs/base/browser/dnd'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; @@ -231,6 +231,14 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt }, accessibilityProvider: options.accessibilityProvider && { ...options.accessibilityProvider, + getPosInSet: undefined, + getSetSize: undefined, + getRole: options.accessibilityProvider!.getRole ? (el) => { + return options.accessibilityProvider!.getRole!(el.element as T); + } : () => 'treeitem', + isChecked: options.accessibilityProvider!.isChecked ? (e) => { + return !!(options.accessibilityProvider?.isChecked!(e.element as T)); + } : undefined, getAriaLabel(e) { return options.accessibilityProvider!.getAriaLabel(e.element as T); }, @@ -258,20 +266,7 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) ) ), - ariaProvider: options.ariaProvider && { - getPosInSet(el, index) { - return options.ariaProvider!.getPosInSet(el.element as T, index); - }, - getSetSize(el, index, listLength) { - return options.ariaProvider!.getSetSize(el.element as T, index, listLength); - }, - getRole: options.ariaProvider!.getRole ? (el) => { - return options.ariaProvider!.getRole!(el.element as T); - } : undefined, - isChecked: options.ariaProvider!.isChecked ? (e) => { - return options.ariaProvider?.isChecked!(e.element as T); - } : undefined - }, + ariaRole: 'tree', additionalScrollHeight: options.additionalScrollHeight }; } @@ -312,7 +307,7 @@ export class AsyncDataTree implements IDisposable private readonly collapseByDefault?: { (e: T): boolean; }; private readonly subTreeRefreshPromises = new Map, Promise>(); - private readonly refreshPromises = new Map, CancelablePromise>(); + private readonly refreshPromises = new Map, CancelablePromise>>(); protected readonly identityProvider?: IIdentityProvider; private readonly autoExpandSingleChildren: boolean; @@ -739,10 +734,10 @@ export class AsyncDataTree implements IDisposable private async doRefreshNode(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise[]> { node.hasChildren = !!this.dataSource.hasChildren(node.element!); - let childrenPromise: Promise; + let childrenPromise: Promise>; if (!node.hasChildren) { - childrenPromise = Promise.resolve([]); + childrenPromise = Promise.resolve(Iterable.empty()); } else { const slowTimeout = timeout(800); @@ -776,7 +771,7 @@ export class AsyncDataTree implements IDisposable } } - private doGetChildren(node: IAsyncDataTreeNode): Promise { + private doGetChildren(node: IAsyncDataTreeNode): Promise> { let result = this.refreshPromises.get(node); if (result) { @@ -790,7 +785,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 { @@ -808,7 +803,9 @@ export class AsyncDataTree implements IDisposable } } - private setChildren(node: IAsyncDataTreeNode, childrenElements: T[], recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): IAsyncDataTreeNode[] { + private setChildren(node: IAsyncDataTreeNode, childrenElementsIterable: Iterable, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): IAsyncDataTreeNode[] { + const childrenElements = [...childrenElementsIterable]; + // perf: if the node was and still is a leaf, avoid all this hassle if (node.children.length === 0 && childrenElements.length === 0) { return []; @@ -942,15 +939,15 @@ export class AsyncDataTree implements IDisposable return { element: node, - children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => this.asTreeElement(child, viewStateContext)) : [], + children: node.hasChildren ? Iterable.map(node.children, child => this.asTreeElement(child, viewStateContext)) : [], collapsible: node.hasChildren, collapsed }; } - protected processChildren(children: T[]): T[] { + protected processChildren(children: Iterable): Iterable { if (this.sorter) { - children.sort(this.sorter.compare.bind(this.sorter)); + children = [...children].sort(this.sorter.compare.bind(this.sorter)); } return children; @@ -1242,9 +1239,9 @@ export class CompressibleAsyncDataTree extends As // For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work // and we have to filter everything beforehand // Related to #85193 and #85835 - protected processChildren(children: T[]): T[] { + protected processChildren(children: Iterable): Iterable { if (this.filter) { - children = children.filter(e => { + children = Iterable.filter(children, e => { const result = this.filter!.filter(e, TreeVisibility.Visible); const visibility = getVisibility(result); diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index 8c0cffe6983..d3a0f95131b 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { ISpliceable } from 'vs/base/common/sequence'; -import { Iterator, ISequence } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { Event } from 'vs/base/common/event'; import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree'; import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; // Exported only for test reasons, do not use directly export interface ICompressedTreeElement extends ITreeElement { - readonly children?: ISequence>; + readonly children?: Iterable>; readonly incompressible?: boolean; } @@ -27,7 +27,7 @@ function noCompress(element: ICompressedTreeElement): ITreeElement(element: ICompressedTreeElement): ITreeElement>; + let childrenIterator: Iterable>; let children: ITreeElement[]; while (true) { - childrenIterator = Iterator.from(element.children); - children = Iterator.collect(childrenIterator, 2); + [children, childrenIterator] = Iterable.consume(Iterable.from(element.children), 2); if (children.length !== 1) { break; @@ -60,19 +59,19 @@ export function compress(element: ICompressedTreeElement): ITreeElement(element: ITreeElement>, index = 0): ICompressedTreeElement { - let children: Iterator>; + let children: Iterable>; if (index < element.element.elements.length - 1) { - children = Iterator.single(_decompress(element, index + 1)); + children = [_decompress(element, index + 1)]; } else { - children = Iterator.map(Iterator.from(element.children), el => _decompress(el, 0)); + children = Iterable.map(Iterable.from(element.children), el => _decompress(el, 0)); } if (index === 0 && element.element.incompressible) { @@ -98,12 +97,12 @@ export function decompress(element: ITreeElement>): IC return _decompress(element, 0); } -function splice(treeElement: ICompressedTreeElement, element: T, children: Iterator>): ICompressedTreeElement { +function splice(treeElement: ICompressedTreeElement, element: T, children: Iterable>): ICompressedTreeElement { if (treeElement.element === element) { return { ...treeElement, children }; } - return { ...treeElement, children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children)) }; + return { ...treeElement, children: Iterable.map(Iterable.from(treeElement.children), e => splice(e, element, children)) }; } interface ICompressedObjectTreeModelOptions extends IObjectTreeModelOptions, TFilterData> { @@ -136,10 +135,10 @@ export class CompressedObjectTreeModel, TFilterData e setChildren( element: T | null, - children: ISequence> | undefined + children: Iterable> = Iterable.empty() ): void { if (element === null) { - const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress); + const compressedChildren = Iterable.map(children, this.enabled ? compress : noCompress); this._setChildren(null, compressedChildren); return; } @@ -155,7 +154,7 @@ export class CompressedObjectTreeModel, TFilterData e const parent = this.model.getNode(compressedParentNode) as ITreeNode, TFilterData>; const decompressedElement = decompress(node); - const splicedElement = splice(decompressedElement, element, Iterator.from(children)); + const splicedElement = splice(decompressedElement, element, children); const recompressedElement = (this.enabled ? compress : noCompress)(splicedElement); const parentChildren = parent.children @@ -176,15 +175,15 @@ export class CompressedObjectTreeModel, TFilterData e this.enabled = enabled; const root = this.model.getNode(); - const rootChildren = Iterator.from(root.children as ITreeNode>[]); - const decompressedRootChildren = Iterator.map(rootChildren, decompress); - const recompressedRootChildren = Iterator.map(decompressedRootChildren, enabled ? compress : noCompress); + const rootChildren = root.children as ITreeNode>[]; + const decompressedRootChildren = Iterable.map(rootChildren, decompress); + const recompressedRootChildren = Iterable.map(decompressedRootChildren, enabled ? compress : noCompress); this._setChildren(null, recompressedRootChildren); } private _setChildren( node: ICompressedTreeNode | null, - children: ISequence>> | undefined + children: Iterable>> ): void { const insertedElements = new Set(); const _onDidCreateNode = (node: ITreeNode, TFilterData>) => { @@ -413,7 +412,7 @@ export class CompressibleObjectTreeModel, TFilterData this.model = new CompressedObjectTreeModel(user, mapList(this.nodeMapper, list), mapOptions(compressedNodeUnwrapper, options)); } - setChildren(element: T | null, children?: ISequence>): void { + setChildren(element: T | null, children: Iterable> = Iterable.empty()): void { this.model.setChildren(element, children); } diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index f14d44e8e4a..cf7e1110e95 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -8,7 +8,7 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource, TreeError } from 'vs/base/browser/ui/tree/tree'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; -import { Iterator } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; export interface IDataTreeOptions extends IAbstractTreeOptions { readonly sorter?: ITreeSorter; @@ -158,9 +158,9 @@ export class DataTree extends AbstractTree boolean | undefined): { elements: Iterator>, size: number } { - const children = this.dataSource.getChildren(element); - const elements = Iterator.map>(Iterator.fromArray(children), element => { + private iterate(element: TInput | T, isCollapsed?: (el: T) => boolean | undefined): { elements: Iterable>, size: number } { + const children = [...this.dataSource.getChildren(element)]; + const elements = Iterable.map(children, element => { const { elements: children, size } = this.iterate(element, isCollapsed); const collapsible = this.dataSource.hasChildren ? this.dataSource.hasChildren(element) : undefined; const collapsed = size === 0 ? undefined : (isCollapsed && isCollapsed(element)); diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index 7cf73ee5fd7..d4be627730e 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/tree'; -import { Iterator, ISequence } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel'; @@ -28,7 +28,7 @@ export class IndexTree extends AbstractTree> = Iterator.empty()): void { + splice(location: number[], deleteCount: number, toInsert: Iterable> = Iterable.empty()): void { this.model.splice(location, deleteCount, toInsert); } diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index b0c1ed8628f..17b860852eb 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -6,7 +6,7 @@ import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeNode, TreeVisibility, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree'; import { tail2 } from 'vs/base/common/arrays'; import { Emitter, Event, EventBufferer } from 'vs/base/common/event'; -import { ISequence, Iterator } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { ISpliceable } from 'vs/base/common/sequence'; // Exported for tests @@ -103,7 +103,7 @@ export class IndexTreeModel, TFilterData = voi splice( location: number[], deleteCount: number, - toInsert?: ISequence>, + toInsert: Iterable> = Iterable.empty(), onDidCreateNode?: (node: ITreeNode) => void, onDidDeleteNode?: (node: ITreeNode) => void ): void { @@ -113,7 +113,7 @@ export class IndexTreeModel, TFilterData = voi const { parentNode, listIndex, revealed, visible } = this.getParentNodeWithListIndex(location); const treeListElementsToInsert: ITreeNode[] = []; - const nodesToInsertIterator = Iterator.map(Iterator.from(toInsert), el => this.createTreeNode(el, parentNode, parentNode.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, revealed, treeListElementsToInsert, onDidCreateNode)); + const nodesToInsertIterator = Iterable.map(toInsert, el => this.createTreeNode(el, parentNode, parentNode.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, revealed, treeListElementsToInsert, onDidCreateNode)); const lastIndex = location[location.length - 1]; @@ -134,14 +134,14 @@ export class IndexTreeModel, TFilterData = voi let insertedVisibleChildrenCount = 0; let renderNodeCount = 0; - Iterator.forEach(nodesToInsertIterator, child => { + for (const child of nodesToInsertIterator) { nodesToInsert.push(child); renderNodeCount += child.renderNodeCount; if (child.visible) { child.visibleChildIndex = visibleChildStartIndex + insertedVisibleChildrenCount++; } - }); + } const deletedNodes = parentNode.children.splice(lastIndex, deleteCount, ...nodesToInsert); @@ -365,21 +365,21 @@ export class IndexTreeModel, TFilterData = voi treeListElements.push(node); } - const childElements = Iterator.from(treeElement.children); + const childElements = treeElement.children || Iterable.empty(); const childRevealed = revealed && visibility !== TreeVisibility.Hidden && !node.collapsed; - const childNodes = Iterator.map(childElements, el => this.createTreeNode(el, node, visibility, childRevealed, treeListElements, onDidCreateNode)); + const childNodes = Iterable.map(childElements, el => this.createTreeNode(el, node, visibility, childRevealed, treeListElements, onDidCreateNode)); let visibleChildrenCount = 0; let renderNodeCount = 1; - Iterator.forEach(childNodes, child => { + for (const child of childNodes) { node.children.push(child); renderNodeCount += child.renderNodeCount; if (child.visible) { child.visibleChildIndex = visibleChildrenCount++; } - }); + } node.collapsible = node.collapsible || node.children.length > 0; node.visibleChildrenCount = visibleChildrenCount; 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/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index a259ad59db0..38647079853 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ISequence } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree'; import { ISpliceable } from 'vs/base/common/sequence'; import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree'; @@ -33,7 +33,7 @@ export class ObjectTree, TFilterData = void> extends super(user, container, delegate, renderers, options as IObjectTreeOptions); } - setChildren(element: T | null, children?: ISequence>): void { + setChildren(element: T | null, children: Iterable> = Iterable.empty()): void { this.model.setChildren(element, children); } @@ -184,7 +184,7 @@ export class CompressibleObjectTree, TFilterData = vo super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options)); } - setChildren(element: T | null, children?: ISequence>): void { + setChildren(element: T | null, children: Iterable> = Iterable.empty()): void { this.model.setChildren(element, children); } diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 1031060c15f..12c839ab10c 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ISpliceable } from 'vs/base/common/sequence'; -import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; import { Event } from 'vs/base/common/event'; import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree'; @@ -14,7 +14,7 @@ import { mergeSort } from 'vs/base/common/arrays'; export type ITreeNodeCallback = (node: ITreeNode) => void; export interface IObjectTreeModel, TFilterData extends NonNullable = void> extends ITreeModel { - setChildren(element: T | null, children: ISequence> | undefined): void; + setChildren(element: T | null, children: Iterable> | undefined): void; resort(element?: T | null, recursive?: boolean): void; } @@ -62,7 +62,7 @@ export class ObjectTreeModel, TFilterData extends Non setChildren( element: T | null, - children: ISequence> | undefined, + children: Iterable> = Iterable.empty(), onDidCreateNode?: ITreeNodeCallback, onDidDeleteNode?: ITreeNodeCallback ): void { @@ -72,7 +72,7 @@ export class ObjectTreeModel, TFilterData extends Non private _setChildren( location: number[], - children: ISequence> | undefined, + children: Iterable> = Iterable.empty(), onDidCreateNode?: ITreeNodeCallback, onDidDeleteNode?: ITreeNodeCallback ): void { @@ -132,14 +132,12 @@ export class ObjectTreeModel, TFilterData extends Non ); } - private preserveCollapseState(elements: ISequence> | undefined): ISequence> { - let iterator = elements ? getSequenceIterator(elements) : Iterator.empty>(); - + private preserveCollapseState(elements: Iterable> = Iterable.empty()): Iterable> { if (this.sorter) { - iterator = Iterator.fromArray(mergeSort(Iterator.collect(iterator), this.sorter.compare.bind(this.sorter))); + elements = mergeSort([...elements], this.sorter.compare.bind(this.sorter)); } - return Iterator.map(iterator, treeElement => { + return Iterable.map(elements, treeElement => { let node = this.nodes.get(treeElement.element); if (!node && this.identityProvider) { @@ -182,14 +180,14 @@ export class ObjectTreeModel, TFilterData extends Non this._setChildren(location, this.resortChildren(node, recursive)); } - private resortChildren(node: ITreeNode, recursive: boolean, first = true): ISequence> { - let childrenNodes = Iterator.fromArray(node.children as ITreeNode[]); + private resortChildren(node: ITreeNode, recursive: boolean, first = true): Iterable> { + let childrenNodes = [...node.children] as ITreeNode[]; if (recursive || first) { - childrenNodes = Iterator.fromArray(Iterator.collect(childrenNodes).sort(this.sorter!.compare.bind(this.sorter))); + childrenNodes = mergeSort(childrenNodes, this.sorter!.compare.bind(this.sorter)); } - return Iterator.map, ITreeElement>(childrenNodes, node => ({ + return Iterable.map, ITreeElement>(childrenNodes, node => ({ element: node.element as T, collapsible: node.collapsible, collapsed: node.collapsed, diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 7e2b282cf82..085bb3d90b7 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { Iterator } from 'vs/base/common/iterator'; import { IListRenderer, IListDragOverReaction, IListDragAndDrop, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { IDragAndDropData } from 'vs/base/browser/dnd'; @@ -74,7 +73,7 @@ export interface ITreeSorter { export interface ITreeElement { readonly element: T; - readonly children?: Iterator> | ITreeElement[]; + readonly children?: Iterable>; readonly collapsible?: boolean; readonly collapsed?: boolean; } @@ -167,12 +166,12 @@ export interface ITreeNavigator { export interface IDataSource { hasChildren?(element: TInput | T): boolean; - getChildren(element: TInput | T): T[]; + getChildren(element: TInput | T): Iterable; } export interface IAsyncDataSource { hasChildren(element: TInput | T): boolean; - getChildren(element: TInput | T): T[] | Promise; + getChildren(element: TInput | T): Iterable | Promise>; } export const enum TreeDragOverBubble { 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/assert.ts b/src/vs/base/common/assert.ts index 1e227df640e..9e2510b4d0b 100644 --- a/src/vs/base/common/assert.ts +++ b/src/vs/base/common/assert.ts @@ -6,8 +6,8 @@ /** * Throws an error with the provided message if the provided value does not evaluate to a true Javascript value. */ -export function ok(value?: any, message?: string) { +export function ok(value?: unknown, message?: string) { if (!value) { - throw new Error(message ? 'Assertion failed (' + message + ')' : 'Assertion Failed'); + throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed'); } } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index ceb38bd5b18..9951ca26b7e 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -56,6 +56,20 @@ export function raceCancellation(promise: Promise, token: CancellationToke return Promise.race([promise, new Promise(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]); } +export function raceTimeout(promise: Promise, timeout: number, onTimeout?: () => void): Promise { + let promiseResolve: (() => void) | undefined = undefined; + + const timer = setTimeout(() => { + promiseResolve?.(); + onTimeout?.(); + }, timeout); + + return Promise.race([ + promise.finally(() => clearTimeout(timer)), + new Promise(resolve => promiseResolve = resolve) + ]); +} + export function asPromise(callback: () => T | Thenable): Promise { return new Promise((resolve, reject) => { const item = callback(); @@ -823,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/date.ts b/src/vs/base/common/date.ts index a6da9d5ae33..443827d587b 100644 --- a/src/vs/base/common/date.ts +++ b/src/vs/base/common/date.ts @@ -4,6 +4,120 @@ *--------------------------------------------------------------------------------------------*/ import { pad } from './strings'; +import { localize } from 'vs/nls'; + +const minute = 60; +const hour = minute * 60; +const day = hour * 24; +const week = day * 7; +const month = day * 30; +const year = day * 365; + +export function fromNow(date: number | Date, appendAgoLabel?: boolean): string { + if (typeof date !== 'number') { + date = date.getTime(); + } + + const seconds = Math.round((new Date().getTime() - date) / 1000); + if (seconds < -30) { + return localize('date.fromNow.in', 'in {0}', fromNow(new Date().getTime() + seconds * 1000, false)); + } + + if (seconds < 30) { + return localize('date.fromNow.now', 'now'); + } + + let value: number; + if (seconds < minute) { + value = seconds; + + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.seconds.singular.ago', '{0} sec ago', value) + : localize('date.fromNow.seconds.plural.ago', '{0} secs ago', value); + } else { + return value === 1 + ? localize('date.fromNow.seconds.singular', '{0} sec', value) + : localize('date.fromNow.seconds.plural', '{0} secs', value); + } + } + + if (seconds < hour) { + value = Math.floor(seconds / minute); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.minutes.singular.ago', '{0} min ago', value) + : localize('date.fromNow.minutes.plural.ago', '{0} mins ago', value); + } else { + return value === 1 + ? localize('date.fromNow.minutes.singular', '{0} min', value) + : localize('date.fromNow.minutes.plural', '{0} mins', value); + } + } + + if (seconds < day) { + value = Math.floor(seconds / hour); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.hours.singular.ago', '{0} hr ago', value) + : localize('date.fromNow.hours.plural.ago', '{0} hrs ago', value); + } else { + return value === 1 + ? localize('date.fromNow.hours.singular', '{0} hr', value) + : localize('date.fromNow.hours.plural', '{0} hrs', value); + } + } + + if (seconds < week) { + value = Math.floor(seconds / day); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.days.singular.ago', '{0} day ago', value) + : localize('date.fromNow.days.plural.ago', '{0} days ago', value); + } else { + return value === 1 + ? localize('date.fromNow.days.singular', '{0} day', value) + : localize('date.fromNow.days.plural', '{0} days', value); + } + } + + if (seconds < month) { + value = Math.floor(seconds / week); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.weeks.singular.ago', '{0} wk ago', value) + : localize('date.fromNow.weeks.plural.ago', '{0} wks ago', value); + } else { + return value === 1 + ? localize('date.fromNow.weeks.singular', '{0} wk', value) + : localize('date.fromNow.weeks.plural', '{0} wks', value); + } + } + + if (seconds < year) { + value = Math.floor(seconds / month); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.months.singular.ago', '{0} mo ago', value) + : localize('date.fromNow.months.plural.ago', '{0} mos ago', value); + } else { + return value === 1 + ? localize('date.fromNow.months.singular', '{0} mo', value) + : localize('date.fromNow.months.plural', '{0} mos', value); + } + } + + value = Math.floor(seconds / year); + if (appendAgoLabel) { + return value === 1 + ? localize('date.fromNow.years.singular.ago', '{0} yr ago', value) + : localize('date.fromNow.years.plural.ago', '{0} yrs ago', value); + } else { + return value === 1 + ? localize('date.fromNow.years.singular', '{0} yr', value) + : localize('date.fromNow.years.plural', '{0} yrs', value); + } +} export function toLocalISOString(date: Date): string { return date.getFullYear() + 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 907dc51ff93..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; } @@ -283,3 +283,20 @@ export function isRootOrDriveLetter(path: string): boolean { return pathNormalized === posix.sep; } + +export function indexOfPath(path: string, candidate: string, ignoreCase: boolean): number { + if (candidate.length > path.length) { + return -1; + } + + if (path === candidate) { + return 0; + } + + if (ignoreCase) { + path = path.toLowerCase(); + candidate = candidate.toLowerCase(); + } + + return path.indexOf(candidate); +} diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 3740a128f1a..71b78527b39 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 }); } } } @@ -559,6 +559,8 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu let patternPos = patternStart; let wordPos = wordStart; + let hasStrongFirstMatch = false; + // There will be a match, fill in tables for (row = 1, patternPos = patternStart; patternPos < patternLen; row++, patternPos++) { @@ -566,6 +568,10 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu const score = _doScore(pattern, patternLow, patternPos, patternStart, word, wordLow, wordPos); + if (patternPos === patternStart && score > 1) { + hasStrongFirstMatch = true; + } + _scores[row][column] = score; const diag = _table[row - 1][column - 1] + (score > 1 ? 1 : score); @@ -604,6 +610,10 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu printTables(pattern, patternStart, word, wordStart); } + if (!hasStrongFirstMatch && !firstMatchCanBeWeak) { + return undefined; + } + _matchesCount = 0; _topScore = -100; _wordStart = wordStart; 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 60% rename from src/vs/base/parts/quickopen/common/quickOpenScorer.ts rename to src/vs/base/common/fuzzyScorer.ts index 21cb08caace..a961f2c5cf9 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,75 @@ 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[] { + + // sort matches by start to be able to normalize + const sortedMatches = matches.sort((matchA, matchB) => { + return matchA.start - matchB.start; + }); + + // merge matches that overlap + const normalizedMatches: IMatch[] = []; + let currentMatch: IMatch | undefined = undefined; + for (const match of sortedMatches) { + + // if we have no current match or the matches + // do not overlap, we take it as is and remember + // it for future merging + if (!currentMatch || !matchOverlaps(currentMatch, match)) { + currentMatch = match; + normalizedMatches.push(match); + } + + // otherwise we merge the matches + else { + currentMatch.start = Math.min(currentMatch.start, match.start); + currentMatch.end = Math.max(currentMatch.end, match.end); + } + } + + return normalizedMatches; +} + +function matchOverlaps(matchA: IMatch, matchB: IMatch): boolean { + if (matchA.end < matchB.start) { + return false; // A ends before B starts + } + + if (matchB.end < matchA.start) { + return false; // B ends before A starts + } + + return true; +} + +//#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 +638,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 +656,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 +721,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 +749,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 78fc5e97ce1..5947cb70276 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -251,14 +251,14 @@ export interface IGlobOptions { } interface ParsedStringPattern { - (path: string, basename: string): string | null | Promise /* the matching pattern */; + (path: string, basename?: string): string | null | Promise /* the matching pattern */; basenames?: string[]; patterns?: string[]; allBasenames?: string[]; allPaths?: string[]; } interface ParsedExpressionPattern { - (path: string, basename: string, name?: string, hasSibling?: (name: string) => boolean | Promise): string | null | Promise /* the matching pattern */; + (path: string, basename?: string, name?: string, hasSibling?: (name: string) => boolean | Promise): string | null | Promise /* the matching pattern */; requiresSiblings?: boolean; allBasenames?: string[]; allPaths?: string[]; @@ -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; @@ -374,7 +374,7 @@ function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern { if (n === 1) { return parsedPatterns[0]; } - const parsedPattern: ParsedStringPattern = function (path: string, basename: string) { + const parsedPattern: ParsedStringPattern = function (path: string, basename?: string) { for (let i = 0, n = parsedPatterns.length; i < n; i++) { if ((parsedPatterns[i])(path, basename)) { return pattern; @@ -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; }; @@ -409,7 +409,7 @@ function trivia4and5(path: string, pattern: string, matchPathEnds: boolean): Par function toRegExp(pattern: string): ParsedStringPattern { try { const regExp = new RegExp(`^${parseRegExp(pattern)}$`); - return function (path: string, basename: string) { + return function (path: string) { regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it! return typeof path === 'string' && regExp.test(path) ? pattern : null; }; @@ -457,7 +457,7 @@ export function parse(arg1: string | IExpression | IRelativePattern, options: IG if (parsedPattern === NULL) { return FALSE; } - const resultPattern: ParsedPattern & { allBasenames?: string[]; allPaths?: string[]; } = function (path: string, basename: string) { + const resultPattern: ParsedPattern & { allBasenames?: string[]; allPaths?: string[]; } = function (path: string, basename?: string) { return !!parsedPattern(path, basename); }; if (parsedPattern.allBasenames) { @@ -540,7 +540,7 @@ function parsedExpression(expression: IExpression, options: IGlobOptions): Parse return parsedPatterns[0]; } - const resultExpression: ParsedStringPattern = function (path: string, basename: string) { + const resultExpression: ParsedStringPattern = function (path: string, basename?: string) { for (let i = 0, n = parsedPatterns.length; i < n; i++) { // Pattern matches path const result = (parsedPatterns[i])(path, basename); @@ -565,7 +565,7 @@ function parsedExpression(expression: IExpression, options: IGlobOptions): Parse return resultExpression; } - const resultExpression: ParsedStringPattern = function (path: string, basename: string, hasSibling?: (name: string) => boolean | Promise) { + const resultExpression: ParsedStringPattern = function (path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise) { let name: string | undefined = undefined; for (let i = 0, n = parsedPatterns.length; i < n; i++) { @@ -620,12 +620,12 @@ function parseExpressionPattern(pattern: string, value: boolean | SiblingClause, if (value) { const when = (value).when; if (typeof when === 'string') { - const result: ParsedExpressionPattern = (path: string, basename: string, name: string, hasSibling: (name: string) => boolean | Promise) => { + const result: ParsedExpressionPattern = (path: string, basename?: string, name?: string, hasSibling?: (name: string) => boolean | Promise) => { if (!hasSibling || !parsedPattern(path, basename)) { return null; } - const clausePattern = when.replace('$(basename)', name); + const clausePattern = when.replace('$(basename)', name!); const matched = hasSibling(clausePattern); return isThenable(matched) ? matched.then(m => m ? 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..f7b8d5ed64d 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INavigator, ArrayNavigator } from 'vs/base/common/iterator'; +import { INavigator, ArrayNavigator } from 'vs/base/common/navigator'; export class HistoryNavigator implements INavigator { @@ -28,21 +28,23 @@ 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 { return this._navigator.current(); } - public parent(): null { - return null; - } - public first(): T | null { return this._navigator.first(); } @@ -73,6 +75,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 9287a6eaebf..38786bdbfa8 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -3,297 +3,75 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export interface IteratorDefinedResult { - readonly done: false; - readonly value: T; -} -export interface IteratorUndefinedResult { - readonly done: true; - readonly value: undefined; -} -export const FIN: IteratorUndefinedResult = { done: true, value: undefined }; -export type IteratorResult = IteratorDefinedResult | IteratorUndefinedResult; +export namespace Iterable { -export interface Iterator { - next(): IteratorResult; -} - -interface NativeIteratorYieldResult { - done?: false; - value: TYield; -} - -interface NativeIteratorReturnResult { - done: true; - value: TReturn; -} - -type NativeIteratorResult = NativeIteratorYieldResult | NativeIteratorReturnResult; - -export interface NativeIterator { - next(): NativeIteratorResult; -} - -export module Iterator { - const _empty: Iterator = { - next() { - return FIN; - } - }; - - export function empty(): Iterator { + const _empty: Iterable = Object.freeze([]); + export function empty(): Iterable { return _empty; } - export function single(value: T): Iterator { - let done = false; - - return { - next(): IteratorResult { - if (done) { - return FIN; - } - - done = true; - return { done: false, value }; - } - }; + export function from(iterable: Iterable | undefined | null): Iterable { + return iterable || _empty; } - export function fromArray(array: ReadonlyArray, index = 0, length = array.length): Iterator { - return { - next(): IteratorResult { - if (index >= length) { - return FIN; - } - - return { done: false, value: array[index++] }; - } - }; + export function first(iterable: Iterable): T | undefined { + return iterable[Symbol.iterator]().next().value; } - export function fromNativeIterator(it: NativeIterator): Iterator { - return { - next(): IteratorResult { - const result = it.next(); - - if (result.done) { - return FIN; - } - - return { done: false, value: result.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 from(elements: Iterator | T[] | undefined): Iterator { - if (!elements) { - return Iterator.empty(); - } else if (Array.isArray(elements)) { - return Iterator.fromArray(elements); - } else { - return elements; + export function* filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable { + for (const element of iterable) { + if (predicate(element)) { + yield element; + } } } - export function map(iterator: Iterator, fn: (t: T) => R): Iterator { - return { - next() { - const element = iterator.next(); - if (element.done) { - return FIN; - } else { - return { done: false, value: fn(element.value) }; - } - } - }; - } - - export function filter(iterator: Iterator, fn: (t: T) => boolean): Iterator { - return { - next() { - while (true) { - const element = iterator.next(); - if (element.done) { - return FIN; - } - if (fn(element.value)) { - return { done: false, value: element.value }; - } - } - } - }; - } - - export function forEach(iterator: Iterator, fn: (t: T) => void): void { - for (let next = iterator.next(); !next.done; next = iterator.next()) { - fn(next.value); + export function* map(iterable: Iterable, fn: (t: T) => R): Iterable { + for (const element of iterable) { + yield fn(element); } } - export function collect(iterator: Iterator, atMost: number = Number.POSITIVE_INFINITY): T[] { - const result: T[] = []; + export function* concat(...iterables: Iterable[]): Iterable { + for (const iterable of iterables) { + for (const element of iterable) { + yield element; + } + } + } + + /** + * Consumes `atMost` elements from iterable and returns the consumed elements, + * and an iterable for the rest of the elements. + */ + export function consume(iterable: Iterable, atMost: number = Number.POSITIVE_INFINITY): [T[], Iterable] { + const consumed: T[] = []; if (atMost === 0) { - return result; + return [consumed, iterable]; } - let i = 0; + const iterator = iterable[Symbol.iterator](); - for (let next = iterator.next(); !next.done; next = iterator.next()) { - result.push(next.value); + for (let i = 0; i < atMost; i++) { + const next = iterator.next(); - if (++i >= atMost) { - break; + if (next.done) { + return [consumed, Iterable.empty()]; } + + consumed.push(next.value); } - return result; - } - - export function concat(...iterators: Iterator[]): Iterator { - let i = 0; - - return { - next() { - if (i >= iterators.length) { - return FIN; - } - - const iterator = iterators[i]; - const result = iterator.next(); - - if (result.done) { - i++; - return this.next(); - } - - return result; - } - }; - } - - export function chain(iterator: Iterator): ChainableIterator { - return new ChainableIterator(iterator); + return [consumed, { [Symbol.iterator]() { return iterator; } }]; } } - -export class ChainableIterator implements Iterator { - - constructor(private it: Iterator) { } - - next(): IteratorResult { return this.it.next(); } - map(fn: (t: T) => R): ChainableIterator { return new ChainableIterator(Iterator.map(this.it, fn)); } - filter(fn: (t: T) => boolean): ChainableIterator { return new ChainableIterator(Iterator.filter(this.it, fn)); } -} - -export type ISequence = Iterator | T[]; - -export function getSequenceIterator(arg: ISequence | undefined): Iterator { - if (Array.isArray(arg)) { - return Iterator.fromArray(arg); - } else if (!arg) { - return Iterator.empty(); - } else { - return arg; - } -} - -export interface INextIterator { - next(): T | null; -} - -export class ArrayIterator implements INextIterator { - - private readonly items: readonly T[]; - protected start: number; - protected end: number; - protected index: number; - - constructor(items: readonly T[], start: number = 0, end: number = items.length, index = start - 1) { - this.items = items; - this.start = start; - this.end = end; - this.index = index; - } - - public first(): T | null { - this.index = this.start; - return this.current(); - } - - public next(): T | null { - this.index = Math.min(this.index + 1, this.end); - return this.current(); - } - - protected current(): T | null { - if (this.index === this.start - 1 || this.index === this.end) { - return null; - } - - return this.items[this.index]; - } -} - -export class ArrayNavigator extends ArrayIterator implements INavigator { - - constructor(items: readonly T[], start: number = 0, end: number = items.length, index = start - 1) { - super(items, start, end, index); - } - - public current(): T | null { - return super.current(); - } - - public previous(): T | null { - this.index = Math.max(this.index - 1, this.start - 1); - return this.current(); - } - - public first(): T | null { - this.index = this.start; - return this.current(); - } - - public last(): T | null { - this.index = this.end - 1; - return this.current(); - } - - public parent(): T | null { - return null; - } -} - -export class MappedIterator implements INextIterator { - - constructor(protected iterator: INextIterator, protected fn: (item: T | null) => R) { - // noop - } - - next() { return this.fn(this.iterator.next()); } -} - -export interface INavigator extends INextIterator { - current(): T | null; - previous(): T | null; - parent(): T | null; - first(): T | null; - last(): T | null; - next(): T | null; -} - -export class MappedNavigator extends MappedIterator implements INavigator { - - constructor(protected navigator: INavigator, fn: (item: T | null) => R) { - super(navigator, fn); - } - - current() { return this.fn(this.navigator.current()); } - previous() { return this.fn(this.navigator.previous()); } - parent() { return this.fn(this.navigator.parent()); } - first() { return this.fn(this.navigator.first()); } - last() { return this.fn(this.navigator.last()); } - next() { return this.fn(this.navigator.next()); } -} diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 86a05d80658..5f3fab86490 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'; @@ -18,7 +18,7 @@ export interface IWorkspaceFolderProvider { } export interface IUserHomeProvider { - userHome: string; + userHome?: URI; } /** @@ -63,8 +63,8 @@ export function getPathLabel(resource: URI | string, userHomeProvider?: IUserHom // normalize and tildify (macOS, Linux only) let res = normalize(resource.fsPath); - if (!isWindows && userHomeProvider) { - res = tildify(res, userHomeProvider.userHome); + if (!isWindows && userHomeProvider?.userHome) { + res = tildify(res, userHomeProvider.userHome.fsPath); } return res; @@ -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/browser/linkedText.ts b/src/vs/base/common/linkedText.ts similarity index 74% rename from src/vs/base/browser/linkedText.ts rename to src/vs/base/common/linkedText.ts index 3d69948aa42..5f6c6a05dcf 100644 --- a/src/vs/base/browser/linkedText.ts +++ b/src/vs/base/common/linkedText.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { memoize } from 'vs/base/common/decorators'; + export interface ILink { readonly label: string; readonly href: string; @@ -10,9 +12,18 @@ export interface ILink { } export type LinkedTextNode = string | ILink; -export type LinkedText = LinkedTextNode[]; -const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; +export class LinkedText { + + constructor(readonly nodes: LinkedTextNode[]) { } + + @memoize + toString(): string { + return this.nodes.map(node => typeof node === 'string' ? node : node.label).join(''); + } +} + +const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: ("|')([^\3]+)(\3))?\)/gi; export function parseLinkedText(text: string): LinkedText { const result: LinkedTextNode[] = []; @@ -25,7 +36,7 @@ export function parseLinkedText(text: string): LinkedText { result.push(text.substring(index, match.index)); } - const [, label, href, title] = match; + const [, label, href, , title] = match; if (title) { result.push({ label, href, title }); @@ -40,5 +51,5 @@ export function parseLinkedText(text: string): LinkedText { result.push(text.substring(index)); } - return result; + return new LinkedText(result); } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 4f6b55c3fe5..5f2af1e753b 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -5,9 +5,13 @@ import { URI } from 'vs/base/common/uri'; import { CharCode } from 'vs/base/common/charCode'; -import { Iterator, IteratorResult, FIN } from './iterator'; - +import { compareSubstringIgnoreCase, compare, compareSubstring } from 'vs/base/common/strings'; +import { Schemas } from 'vs/base/common/network'; +import { isLinux } from 'vs/base/common/platform'; +/** + * @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 +20,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,28 +58,8 @@ 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; +export interface IKeyIterator { + reset(key: K): this; next(): this; hasNext(): boolean; @@ -80,7 +67,7 @@ export interface IKeyIterator { value(): string; } -export class StringIterator implements IKeyIterator { +export class StringIterator implements IKeyIterator { private _value: string = ''; private _pos: number = 0; @@ -111,13 +98,16 @@ export class StringIterator implements IKeyIterator { } } -export class PathIterator implements IKeyIterator { +export class PathIterator implements IKeyIterator { private _value!: string; private _from!: number; private _to!: number; - constructor(private _splitOnBackslash: boolean = true) { } + constructor( + private readonly _splitOnBackslash: boolean = true, + private readonly _caseSensitive: boolean = true + ) { } reset(key: string): this { this._value = key.replace(/\\$|\/$/, ''); @@ -150,27 +140,9 @@ export class PathIterator implements IKeyIterator { } cmp(a: string): number { - - let aPos = 0; - const aLen = a.length; - let thisPos = this._from; - - while (aPos < aLen && thisPos < this._to) { - const cmp = a.charCodeAt(aPos) - this._value.charCodeAt(thisPos); - if (cmp !== 0) { - return cmp; - } - aPos += 1; - thisPos += 1; - } - - if (aLen === this._to - this._from) { - return 0; - } else if (aPos < aLen) { - return -1; - } else { - return 1; - } + return this._caseSensitive + ? compareSubstring(a, this._value, 0, a.length, this._from, this._to) + : compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to); } value(): string { @@ -178,33 +150,122 @@ export class PathIterator implements IKeyIterator { } } -class TernarySearchTreeNode { +const enum UriIteratorState { + Scheme = 1, Authority = 2, Path = 3, Query = 4, Fragment = 5 +} + +export class UriIterator implements IKeyIterator { + + private _pathIterator!: PathIterator; + private _value!: URI; + private _states: UriIteratorState[] = []; + private _stateIdx: number = 0; + + reset(key: URI): this { + this._value = key; + this._states = []; + if (this._value.scheme) { + this._states.push(UriIteratorState.Scheme); + } + if (this._value.authority) { + this._states.push(UriIteratorState.Authority); + } + if (this._value.path) { + //todo@jrieken the case-sensitive logic is copied form `resources.ts#hasToIgnoreCase` + // which cannot be used because it depends on this + const caseSensitive = key.scheme === Schemas.file && isLinux; + this._pathIterator = new PathIterator(false, caseSensitive); + this._pathIterator.reset(key.path); + if (this._pathIterator.value()) { + this._states.push(UriIteratorState.Path); + } + } + if (this._value.query) { + this._states.push(UriIteratorState.Query); + } + if (this._value.fragment) { + this._states.push(UriIteratorState.Fragment); + } + this._stateIdx = 0; + return this; + } + + next(): this { + if (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) { + this._pathIterator.next(); + } else { + this._stateIdx += 1; + } + return this; + } + + hasNext(): boolean { + return (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) + || this._stateIdx < this._states.length - 1; + } + + cmp(a: string): number { + if (this._states[this._stateIdx] === UriIteratorState.Scheme) { + return compareSubstringIgnoreCase(a, this._value.scheme); + } else if (this._states[this._stateIdx] === UriIteratorState.Authority) { + return compareSubstringIgnoreCase(a, this._value.authority); + } else if (this._states[this._stateIdx] === UriIteratorState.Path) { + return this._pathIterator.cmp(a); + } else if (this._states[this._stateIdx] === UriIteratorState.Query) { + return compare(a, this._value.query); + } else if (this._states[this._stateIdx] === UriIteratorState.Fragment) { + return compare(a, this._value.fragment); + } + throw new Error(); + } + + value(): string { + if (this._states[this._stateIdx] === UriIteratorState.Scheme) { + return this._value.scheme; + } else if (this._states[this._stateIdx] === UriIteratorState.Authority) { + return this._value.authority; + } else if (this._states[this._stateIdx] === UriIteratorState.Path) { + return this._pathIterator.value(); + } else if (this._states[this._stateIdx] === UriIteratorState.Query) { + return this._value.query; + } else if (this._states[this._stateIdx] === UriIteratorState.Fragment) { + return this._value.fragment; + } + throw new Error(); + } +} + +class TernarySearchTreeNode { segment!: string; - value: E | undefined; - key!: string; - left: TernarySearchTreeNode | undefined; - mid: TernarySearchTreeNode | undefined; - right: TernarySearchTreeNode | undefined; + value: V | undefined; + key!: K; + left: TernarySearchTreeNode | undefined; + mid: TernarySearchTreeNode | undefined; + right: TernarySearchTreeNode | undefined; isEmpty(): boolean { return !this.left && !this.mid && !this.right && !this.value; } } -export class TernarySearchTree { +export class TernarySearchTree { - static forPaths(): TernarySearchTree { - return new TernarySearchTree(new PathIterator()); + static forUris(): TernarySearchTree { + return new TernarySearchTree(new UriIterator()); } - static forStrings(): TernarySearchTree { - return new TernarySearchTree(new StringIterator()); + static forPaths(): TernarySearchTree { + return new TernarySearchTree(new PathIterator()); } - private _iter: IKeyIterator; - private _root: TernarySearchTreeNode | undefined; + static forStrings(): TernarySearchTree { + return new TernarySearchTree(new StringIterator()); + } - constructor(segments: IKeyIterator) { + private _iter: IKeyIterator; + private _root: TernarySearchTreeNode | undefined; + + constructor(segments: IKeyIterator) { this._iter = segments; } @@ -212,12 +273,12 @@ export class TernarySearchTree { this._root = undefined; } - set(key: string, element: E): E | undefined { + set(key: K, element: V): V | undefined { const iter = this._iter.reset(key); - let node: TernarySearchTreeNode; + let node: TernarySearchTreeNode; if (!this._root) { - this._root = new TernarySearchTreeNode(); + this._root = new TernarySearchTreeNode(); this._root.segment = iter.value(); } @@ -227,7 +288,7 @@ export class TernarySearchTree { if (val > 0) { // left if (!node.left) { - node.left = new TernarySearchTreeNode(); + node.left = new TernarySearchTreeNode(); node.left.segment = iter.value(); } node = node.left; @@ -235,7 +296,7 @@ export class TernarySearchTree { } else if (val < 0) { // right if (!node.right) { - node.right = new TernarySearchTreeNode(); + node.right = new TernarySearchTreeNode(); node.right.segment = iter.value(); } node = node.right; @@ -244,7 +305,7 @@ export class TernarySearchTree { // mid iter.next(); if (!node.mid) { - node.mid = new TernarySearchTreeNode(); + node.mid = new TernarySearchTreeNode(); node.mid.segment = iter.value(); } node = node.mid; @@ -258,7 +319,7 @@ export class TernarySearchTree { return oldElement; } - get(key: string): E | undefined { + get(key: K): V | undefined { const iter = this._iter.reset(key); let node = this._root; while (node) { @@ -280,10 +341,10 @@ export class TernarySearchTree { return node ? node.value : undefined; } - delete(key: string): void { + delete(key: K): void { const iter = this._iter.reset(key); - const stack: [-1 | 0 | 1, TernarySearchTreeNode][] = []; + const stack: [-1 | 0 | 1, TernarySearchTreeNode][] = []; let node = this._root; // find and unset node @@ -321,10 +382,10 @@ export class TernarySearchTree { } } - findSubstr(key: string): E | undefined { + findSubstr(key: K): V | undefined { const iter = this._iter.reset(key); let node = this._root; - let candidate: E | undefined = undefined; + let candidate: V | undefined = undefined; while (node) { const val = iter.cmp(node.segment); if (val > 0) { @@ -345,7 +406,7 @@ export class TernarySearchTree { return node && node.value || candidate; } - findSuperstr(key: string): Iterator | undefined { + findSuperstr(key: K): Iterator | undefined { const iter = this._iter.reset(key); let node = this._root; while (node) { @@ -372,11 +433,11 @@ export class TernarySearchTree { return undefined; } - private _nodeIterator(node: TernarySearchTreeNode): Iterator { - let res: { done: false; value: E; }; + private _nodeIterator(node: TernarySearchTreeNode): Iterator { + let res: { done: false; value: V; }; let idx: number; - let data: E[]; - const next = (): IteratorResult => { + let data: V[]; + const next = (): IteratorResult => { if (!data) { // lazy till first invocation data = []; @@ -384,7 +445,7 @@ export class TernarySearchTree { this._forEach(node, value => data.push(value)); } if (idx >= data.length) { - return FIN; + return { done: true, value: undefined }; } if (!res) { @@ -397,11 +458,11 @@ export class TernarySearchTree { return { next }; } - forEach(callback: (value: E, index: string) => any) { + forEach(callback: (value: V, index: K) => any) { this._forEach(this._root, callback); } - private _forEach(node: TernarySearchTreeNode | undefined, callback: (value: E, index: string) => any) { + private _forEach(node: TernarySearchTreeNode | undefined, callback: (value: V, index: K) => any) { if (node) { // left this._forEach(node.left, callback); 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/navigator.ts b/src/vs/base/common/navigator.ts new file mode 100644 index 00000000000..ba7feffef57 --- /dev/null +++ b/src/vs/base/common/navigator.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. + *--------------------------------------------------------------------------------------------*/ + +export interface INavigator { + current(): T | null; + previous(): T | null; + first(): T | null; + last(): T | null; + next(): T | null; +} + +export class ArrayNavigator implements INavigator { + + constructor( + private readonly items: readonly T[], + protected start: number = 0, + protected end: number = items.length, + protected index = start - 1 + ) { } + + current(): T | null { + if (this.index === this.start - 1 || this.index === this.end) { + return null; + } + + return this.items[this.index]; + } + + next(): T | null { + this.index = Math.min(this.index + 1, this.end); + return this.current(); + } + + previous(): T | null { + this.index = Math.max(this.index - 1, this.start - 1); + return this.current(); + } + + first(): T | null { + this.index = this.start; + return this.current(); + } + + last(): T | null { + this.index = this.end - 1; + return this.current(); + } +} 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..ef537317389 100644 --- a/src/vs/base/common/resourceTree.ts +++ b/src/vs/base/common/resourceTree.ts @@ -5,18 +5,16 @@ import { memoize } from 'vs/base/common/decorators'; 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; readonly relativePath: string; readonly name: string; readonly element: T | undefined; - readonly children: Iterator>; + readonly children: Iterable>; readonly childrenCount: number; readonly parent: IResourceNode | undefined; readonly context: C; @@ -31,8 +29,8 @@ class Node implements IResourceNode { return this._children.size; } - get children(): Iterator> { - return Iterator.fromArray(mapValues(this._children)); + get children(): Iterable> { + return [...values(this._children)]; } @memoize @@ -70,7 +68,9 @@ function collect(node: IResourceNode, result: T[]): T[] { result.push(node.element); } - Iterator.forEach(node.children, child => collect(child, result)); + for (const child of node.children) { + collect(child, result); + } return result; } diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 05610a4750b..a4d1a870a2b 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -5,7 +5,7 @@ import * as extpath from 'vs/base/common/extpath'; import * as paths from 'vs/base/common/path'; -import { URI } from 'vs/base/common/uri'; +import { URI, uriToFsPath } from 'vs/base/common/uri'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows } from 'vs/base/common/platform'; @@ -13,8 +13,20 @@ 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 { + return uriToFsPath(uri, true); +} + +/** + * 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 +41,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 +81,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 +102,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 +126,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 +157,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 +213,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 +222,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++) { @@ -326,7 +317,7 @@ export namespace DataUri { export class ResourceGlobMatcher { private readonly globalExpression: ParsedExpression; - private readonly expressionsByRoot: TernarySearchTree<{ root: URI, expression: ParsedExpression }> = TernarySearchTree.forPaths<{ root: URI, expression: ParsedExpression }>(); + private readonly expressionsByRoot: TernarySearchTree = TernarySearchTree.forUris<{ root: URI, expression: ParsedExpression }>(); constructor( globalExpression: IExpression, @@ -334,12 +325,12 @@ export class ResourceGlobMatcher { ) { this.globalExpression = parse(globalExpression); for (const expression of rootExpressions) { - this.expressionsByRoot.set(expression.root.toString(), { root: expression.root, expression: parse(expression.expression) }); + this.expressionsByRoot.set(expression.root, { root: expression.root, expression: parse(expression.expression) }); } } matches(resource: URI): boolean { - const rootExpression = this.expressionsByRoot.findSubstr(resource.toString()); + const rootExpression = this.expressionsByRoot.findSubstr(resource); if (rootExpression) { const path = relativePath(rootExpression.root, resource); if (path && !!rootExpression.expression(path)) { 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/stream.ts b/src/vs/base/common/stream.ts index 1172496a897..8234770bd19 100644 --- a/src/vs/base/common/stream.ts +++ b/src/vs/base/common/stream.ts @@ -95,8 +95,8 @@ export interface WriteableStream extends ReadableStream { end(result?: T | Error): void; } -export function isReadableStream(obj: any): obj is ReadableStream { - const candidate: ReadableStream = obj; +export function isReadableStream(obj: unknown): obj is ReadableStream { + const candidate = obj as ReadableStream; return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function'); } diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 8d67e493772..2eca10e8038 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; @@ -240,7 +240,7 @@ export function regExpFlags(regexp: RegExp): string { return (regexp.global ? 'g' : '') + (regexp.ignoreCase ? 'i' : '') + (regexp.multiline ? 'm' : '') - + ((regexp as any).unicode ? 'u' : ''); + + ((regexp as any /* standalone editor compilation */).unicode ? 'u' : ''); } /** @@ -295,47 +295,69 @@ export function compare(a: string, b: string): number { } } +export function compareSubstring(a: string, b: string, aStart: number = 0, aEnd: number = a.length, bStart: number = 0, bEnd: number = b.length): number { + for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) { + let codeA = a.charCodeAt(aStart); + let codeB = b.charCodeAt(bStart); + if (codeA < codeB) { + return -1; + } else if (codeA > codeB) { + return 1; + } + } + const aLen = aEnd - aStart; + const bLen = bEnd - bStart; + if (aLen < bLen) { + return -1; + } else if (aLen > bLen) { + return 1; + } + return 0; +} + export function compareIgnoreCase(a: string, b: string): number { - const len = Math.min(a.length, b.length); - for (let i = 0; i < len; i++) { - let codeA = a.charCodeAt(i); - let codeB = b.charCodeAt(i); + return compareSubstringIgnoreCase(a, b, 0, a.length, 0, b.length); +} + +export function compareSubstringIgnoreCase(a: string, b: string, aStart: number = 0, aEnd: number = a.length, bStart: number = 0, bEnd: number = b.length): number { + + for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) { + + let codeA = a.charCodeAt(aStart); + let codeB = b.charCodeAt(bStart); if (codeA === codeB) { // equal continue; } - if (isUpperAsciiLetter(codeA)) { - codeA += 32; - } - - if (isUpperAsciiLetter(codeB)) { - codeB += 32; - } - const diff = codeA - codeB; - - if (diff === 0) { - // equal -> ignoreCase + if (diff === 32 && isUpperAsciiLetter(codeB)) { //codeB =[65-90] && codeA =[97-122] continue; - } else if (isLowerAsciiLetter(codeA) && isLowerAsciiLetter(codeB)) { + } else if (diff === -32 && isUpperAsciiLetter(codeA)) { //codeB =[97-122] && codeA =[65-90] + continue; + } + + if (isLowerAsciiLetter(codeA) && isLowerAsciiLetter(codeB)) { // return diff; } else { - return compare(a.toLowerCase(), b.toLowerCase()); + return compareSubstring(a.toLowerCase(), b.toLowerCase(), aStart, aEnd, bStart, bEnd); } } - if (a.length < b.length) { + const aLen = aEnd - aStart; + const bLen = bEnd - bStart; + + if (aLen < bLen) { return -1; - } else if (a.length > b.length) { + } else if (aLen > bLen) { return 1; - } else { - return 0; } + + return 0; } export function isLowerAsciiLetter(code: number): boolean { @@ -428,66 +450,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 +479,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 +493,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 +835,6 @@ export function removeAnsiEscapeCodes(str: string): string { return str; } -export const removeAccents: (str: string) => string = (function () { - if (typeof (String.prototype as any).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).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 460e1a910ae..42eea76fe40 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -3,45 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const _typeof = { - number: 'number', - string: 'string', - undefined: 'undefined', - object: 'object', - function: 'function' -}; +import { URI, UriComponents } from 'vs/base/common/uri'; /** * @returns whether the provided parameter is a JavaScript Array or not. */ export function isArray(array: any): array is any[] { - 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)); } /** @@ -53,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) @@ -65,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); } @@ -156,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'); } /** @@ -262,3 +240,21 @@ export type AddFirstParameterToFunctions = { [K in keyof T]: T[K] extends URI + ? UriComponents + : UriDto }; + +/** + * Mapped-type that replaces all occurrences of URI with UriComponents and + * drops all functions. + * todo@joh use toJSON-results + */ +export type Dto = { [K in keyof T]: T[K] extends URI + ? UriComponents + : T[K] extends Function + ? never + : UriDto }; diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 78c9fda1d71..a278e5a5ef4 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 uriToFsPath(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(uriToFsPath(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 = uriToFsPath(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 { +export function uriToFsPath(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/crypto.ts b/src/vs/base/node/crypto.ts index f18503ab882..7323a92770a 100644 --- a/src/vs/base/node/crypto.ts +++ b/src/vs/base/node/crypto.ts @@ -5,19 +5,17 @@ import * as fs from 'fs'; import * as crypto from 'crypto'; -import * as stream from 'stream'; import { once } from 'vs/base/common/functional'; export function checksum(path: string, sha1hash: string | undefined): Promise { const promise = new Promise((c, e) => { const input = fs.createReadStream(path); const hash = crypto.createHash('sha1'); - const hashStream = hash as any as stream.PassThrough; - input.pipe(hashStream); + input.pipe(hash); const done = once((err?: Error, result?: string) => { input.removeAllListeners(); - hashStream.removeAllListeners(); + hash.removeAllListeners(); if (err) { e(err); @@ -28,8 +26,8 @@ export function checksum(path: string, sha1hash: string | undefined): Promise done(undefined, data.toString('hex'))); + hash.once('error', done); + hash.once('data', (data: Buffer) => done(undefined, data.toString('hex'))); }); return promise.then(hash => { diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 4a9462c646a..ec3392a780e 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -138,14 +138,6 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions }); } -export function decode(buffer: Buffer, encoding: string): string { - return iconv.decode(buffer, toNodeEncoding(encoding)); -} - -export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer { - return iconv.encode(content as string /* TODO report into upstream typings */, toNodeEncoding(encoding), options); -} - export function encodingExists(encoding: string): boolean { return iconv.encodingExists(toNodeEncoding(encoding)); } @@ -158,7 +150,7 @@ export function encodeStream(encoding: string, options?: { addBOM?: boolean }): return iconv.encodeStream(toNodeEncoding(encoding), options); } -function toNodeEncoding(enc: string | null): string { +export function toNodeEncoding(enc: string | null): string { if (enc === UTF8_with_bom || enc === null) { return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it } diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index c2faa20632f..2799ffc718d 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -21,7 +21,7 @@ import { getMac } from 'vs/base/node/macAddress'; // Sun xVM VirtualBox 08-00-27 export const virtualMachineHint: { value(): number } = new class { - private _virtualMachineOUIs?: TernarySearchTree; + private _virtualMachineOUIs?: TernarySearchTree; private _value?: number; private _isVirtualMachineMacAdress(mac: string): boolean { diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 6cf71160c50..b26e67c4b76 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -9,12 +9,10 @@ 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'; import { normalizeNFC } from 'vs/base/common/normalization'; -import { encode } from 'vs/base/node/encoding'; // See https://github.com/Microsoft/vscode/issues/30180 const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB @@ -293,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; @@ -320,10 +318,6 @@ function ensureWriteFileQueue(queueKey: string): Queue { export interface IWriteFileOptions { mode?: number; flag?: string; - encoding?: { - charset: string; - addBOM: boolean; - }; } interface IEnsuredWriteFileOptions extends IWriteFileOptions { @@ -339,10 +333,6 @@ let canFlush = true; // // See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194 function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void { - if (options.encoding) { - data = encode(data instanceof Uint8Array ? Buffer.from(data) : data, options.encoding.charset, { addBOM: options.encoding.addBOM }); - } - if (!canFlush) { return fs.writeFile(path, data, { mode: options.mode, flag: options.flag }, callback); } @@ -378,10 +368,6 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void { const ensuredOptions = ensureWriteOptions(options); - if (ensuredOptions.encoding) { - data = encode(data, ensuredOptions.encoding.charset, { addBOM: ensuredOptions.encoding.addBOM }); - } - if (!canFlush) { return fs.writeFileSync(path, data, { mode: ensuredOptions.mode, flag: ensuredOptions.flag }); } @@ -413,8 +399,7 @@ function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptio return { mode: typeof options.mode === 'number' ? options.mode : 0o666, - flag: typeof options.flag === 'string' ? options.flag : 'w', - encoding: options.encoding + flag: typeof options.flag === 'string' ? options.flag : 'w' }; } @@ -506,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/node/stream.ts b/src/vs/base/node/stream.ts index 16b73835bd1..12ba5e5d929 100644 --- a/src/vs/base/node/stream.ts +++ b/src/vs/base/node/stream.ts @@ -3,75 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; import { VSBufferReadableStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer'; import { Readable } from 'stream'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { UTF8, UTF8_with_bom, UTF8_BOM, UTF16be, UTF16le_BOM, UTF16be_BOM, UTF16le, UTF_ENCODING } from 'vs/base/node/encoding'; -/** - * Reads a file until a matching string is found. - * - * @param file The file to read. - * @param matchingString The string to search for. - * @param chunkBytes The number of bytes to read each iteration. - * @param maximumBytesToRead The maximum number of bytes to read before giving up. - * @param callback The finished callback. - */ -export function readToMatchingString(file: string, matchingString: string, chunkBytes: number, maximumBytesToRead: number): Promise { - return new Promise((resolve, reject) => - fs.open(file, 'r', null, (err, fd) => { - if (err) { - return reject(err); - } - - function end(err: Error | null, result: string | null): void { - fs.close(fd, closeError => { - if (closeError) { - return reject(closeError); - } - - if (err && (err).code === 'EISDIR') { - return reject(err); // we want to bubble this error up (file is actually a folder) - } - - return resolve(result); - }); - } - - const buffer = Buffer.allocUnsafe(maximumBytesToRead); - let offset = 0; - - function readChunk(): void { - fs.read(fd, buffer, offset, chunkBytes, null, (err, bytesRead) => { - if (err) { - return end(err, null); - } - - if (bytesRead === 0) { - return end(null, null); - } - - offset += bytesRead; - - const newLineIndex = buffer.indexOf(matchingString); - if (newLineIndex >= 0) { - return end(null, buffer.toString('utf8').substr(0, newLineIndex)); - } - - if (offset >= maximumBytesToRead) { - return end(new Error(`Could not find ${matchingString} in first ${maximumBytesToRead} bytes of ${file}`), null); - } - - return readChunk(); - }); - } - - readChunk(); - }) - ); -} - export function streamToNodeReadable(stream: VSBufferReadableStream): Readable { return new class extends Readable { private listening = false; 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..87091726f9a 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,7 +196,13 @@ align-items: center; } -.quick-input-list .quick-input-list-rows > .quick-input-list-row .codicon { +.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[class*='codicon-'] { + color: currentColor !important; vertical-align: sub; } @@ -194,6 +210,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 +225,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 +254,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 059eb29406d..e6c6676aae2 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 = []; @@ -397,13 +399,21 @@ class QuickPick extends QuickInput implements IQuickPi private _valueSelection: Readonly<[number, number]> | undefined; private valueSelectionUpdated = true; private _validationMessage: string | undefined; - private _ok = false; + private _ok: boolean | 'default' = 'default'; 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; } @@ -566,27 +609,36 @@ class QuickPick extends QuickInput implements IQuickPi return this._ok; } - set ok(showOkButton: boolean) { + set ok(showOkButton: boolean | 'default') { this._ok = showOkButton; 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,27 +683,47 @@ 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; + case KeyCode.Home: + if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { + this.ui.list.focus(QuickInputListFocus.First); + event.preventDefault(); + } + break; + case KeyCode.End: + if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { + this.ui.list.focus(QuickInputListFocus.Last); + event.preventDefault(); + } + break; } })); this.visibleDisposables.add(this.ui.onDidAccept(() => { @@ -657,10 +731,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 +746,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 +759,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 +781,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 +789,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 +798,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 +819,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,7 +837,31 @@ class QuickPick extends QuickInput implements IQuickPi if (!this.visible) { return; } - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, description: !!this.description, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: this.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: this.ok }); + let hideInput = false; + let inputShownJustForScreenReader = false; + if (!!this._hideInput && this._items.length > 0) { + if (this.ui.isScreenReaderOptimized()) { + // Always show input if screen reader attached https://github.com/microsoft/vscode/issues/94360 + inputShownJustForScreenReader = true; + } else { + hideInput = true; + } + } + 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; @@ -769,14 +873,38 @@ class QuickPick extends QuickInput implements IQuickPi if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { this.ui.inputBox.placeholder = (this.placeholder || ''); } + if (inputShownJustForScreenReader) { + this.ui.inputBox.ariaLabel = ''; + } else 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) { @@ -814,12 +942,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(); + } } } @@ -906,7 +1034,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(); @@ -943,11 +1071,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; @@ -960,10 +1088,14 @@ export class QuickInputController extends Disposable { private parentElement: HTMLElement; private styles: IQuickInputStyles; - private onShowEmitter = new Emitter(); - private onHideEmitter = new Emitter(); - public onShow = this.onShowEmitter.event; - public onHide = this.onHideEmitter.event; + + private onShowEmitter = this._register(new Emitter()); + readonly onShow = this.onShowEmitter.event; + + private onHideEmitter = this._register(new Emitter()); + readonly onHide = this.onHideEmitter.event; + + private previousFocusElement?: HTMLElement; constructor(private options: IQuickInputOptions) { super(); @@ -974,30 +1106,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() { @@ -1098,7 +1213,11 @@ export class QuickInputController extends Disposable { const focusTracker = dom.trackFocus(container); this._register(focusTracker); + this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, e => { + this.previousFocusElement = e.relatedTarget instanceof HTMLElement ? e.relatedTarget : undefined; + }, true)); this._register(focusTracker.onDidBlur(() => { + this.previousFocusElement = undefined; if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) { this.hide(true); } @@ -1180,9 +1299,10 @@ export class QuickInputController extends Disposable { return this.ui; } - pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise { - return new Promise((doResolve, reject) => { - let resolve = (result: any) => { + pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { + type R = (O extends { canPickMany: true } ? T[] : T) | undefined; + return new Promise((doResolve, reject) => { + let resolve = (result: R) => { resolve = doResolve; if (options.onKeyMods) { options.onKeyMods(input.keyMods); @@ -1199,12 +1319,12 @@ export class QuickInputController extends Disposable { input, input.onDidAccept(() => { if (input.canSelectMany) { - resolve(input.selectedItems.slice()); + resolve(input.selectedItems.slice()); input.hide(); } else { const result = input.activeItems[0]; if (result) { - resolve(result); + resolve(result); input.hide(); } } @@ -1219,7 +1339,7 @@ export class QuickInputController extends Disposable { if (!input.canSelectMany) { const result = items[0]; if (result) { - resolve(result); + resolve(result); input.hide(); } } @@ -1250,6 +1370,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; @@ -1278,7 +1401,7 @@ export class QuickInputController extends Disposable { }); } - input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise { + input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise { return new Promise((resolve, reject) => { if (token.isCancellationRequested) { resolve(undefined); @@ -1377,7 +1500,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"); @@ -1398,6 +1521,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 @@ -1437,14 +1561,19 @@ export class QuickInputController extends Disposable { } } - public hide(focusLost?: boolean) { + hide(focusLost?: boolean) { const controller = this.controller; if (controller) { this.controller = null; this.onHideEmitter.fire(); this.getUI().container.style.display = 'none'; if (!focusLost) { - this.options.returnFocus(); + if (this.previousFocusElement && this.previousFocusElement.offsetParent) { + this.previousFocusElement.focus(); + this.previousFocusElement = undefined; + } else { + this.options.returnFocus(); + } } controller.didHide(); } @@ -1464,29 +1593,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(); @@ -1506,7 +1639,7 @@ export class QuickInputController extends Disposable { } } - public applyStyles(styles: IQuickInputStyles) { + applyStyles(styles: IQuickInputStyles) { this.styles = styles; this.updateStyles(); } @@ -1514,13 +1647,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 4aa083e7eba..a6c3a99c82b 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, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; const $ = dom.$; @@ -33,17 +34,22 @@ 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; } -class ListElement implements IListElement { +class ListElement implements IListElement, IDisposable { index!: number; item!: IQuickPickItem; saneLabel!: string; + saneAriaLabel!: string; saneDescription?: string; saneDetail?: string; hidden = false; @@ -68,12 +74,17 @@ class ListElement implements IListElement { constructor(init: IListElement) { assign(this, init); } + + dispose() { + this._onChecked.dispose(); + } } interface IListElementTemplateData { entry: HTMLDivElement; checkbox: HTMLInputElement; label: IconLabel; + keybinding: KeybindingLabel; detail: HighlightedLabel; separator: HTMLDivElement; actionBar: ActionBar; @@ -99,6 +110,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 +129,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 +190,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 +231,16 @@ class ListElementDelegate implements IListVirtualDelegate { } } +export enum QuickInputListFocus { + First = 1, + Second, + Last, + Next, + Previous, + NextPage, + PreviousPage +} + export class QuickInputList { readonly id: string; @@ -244,12 +277,15 @@ 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, + ariaRole: 'listbox' } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); @@ -265,14 +301,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 +325,39 @@ 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]); + } + })); + this.disposables.push( + this._onChangedAllVisibleChecked, + this._onChangedCheckedCount, + this._onChangedVisibleCount, + this._onChangedCheckedElements, + this._onButtonTriggered, + this._onLeave, + ); } @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 +421,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 @@ -377,6 +446,7 @@ export class QuickInputList { } return result; }, [] as ListElement[]); + this.elementDisposables.push(...this.elements); this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents()))); this.elementsToIndexes = this.elements.reduce((map, element, index) => { @@ -388,6 +458,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 +472,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,23 +516,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 === 'Previous' || what === 'PreviousPage') && this.list.getFocus()[0] === 0) { - what = 'Last'; + if (what === QuickInputListFocus.Next && this.list.getFocus()[0] === this.list.length - 1) { + what = QuickInputListFocus.First; } - (this.list as any)['focus' + what](); - this.list.reveal(this.list.getFocus()[0]); + 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); + } } clearFocus() { @@ -471,9 +579,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(); @@ -531,6 +640,8 @@ export class QuickInputList { this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked()); this._onChangedVisibleCount.fire(shownElements.length); + + return true; } toggleCheckbox() { @@ -589,5 +700,20 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s return 1; } + if (labelHighlightsA.length === 0 && labelHighlightsB.length === 0) { + return 0; + } + return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor); } + +class QuickInputAccessibilityProvider implements IListAccessibilityProvider { + + getAriaLabel(element: ListElement): string | null { + return element.saneAriaLabel; + } + + getRole() { + return 'option'; + } +} diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 5640fecc09b..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[]; } @@ -113,7 +135,7 @@ export interface IInputOptions { placeHolder?: string; /** - * set to true to show a password prompt that will not show the typed value + * Controls if a password input is shown. Password input hides the typed text. */ password?: boolean; @@ -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,23 +171,50 @@ 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; - ok: boolean; + /** + * 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'; readonly onDidCustom: Event; @@ -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 03dedeca57f..b6602b6a626 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -18,16 +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 { - items: Map; + readonly changed?: Map; + readonly deleted?: Set; } export interface IStorageDatabase { @@ -73,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(); } @@ -104,10 +103,11 @@ export class Storage extends Disposable implements IStorage { // items that change external require us to update our // caches with the values. we just accept the value and // emit an event if there is a change. - e.items.forEach((value, key) => this.accept(key, value)); + e.changed?.forEach((value, key) => this.accept(key, value)); + e.deleted?.forEach(key => this.accept(key, undefined)); } - private accept(key: string, value: string): void { + private accept(key: string, value: string | undefined): void { if (this.state === StorageState.Closed) { return; // Return early if we are already closed } @@ -144,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; @@ -153,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(); @@ -294,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)); } @@ -308,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(); - } -} \ No newline at end of file + 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/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index 9c966c0868b..5c25b92dffc 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -124,28 +124,27 @@ suite('Storage Library', () => { changes.clear(); // Nothing happens if changing to same value - const change = new Map(); - change.set('foo', 'bar'); - database.fireDidChangeItemsExternal({ items: change }); + const changed = new Map(); + changed.set('foo', 'bar'); + database.fireDidChangeItemsExternal({ changed }); equal(changes.size, 0); // Change is accepted if valid - change.set('foo', 'bar1'); - database.fireDidChangeItemsExternal({ items: change }); + changed.set('foo', 'bar1'); + database.fireDidChangeItemsExternal({ changed }); ok(changes.has('foo')); equal(storage.get('foo'), 'bar1'); changes.clear(); // Delete is accepted - change.set('foo', undefined!); - database.fireDidChangeItemsExternal({ items: change }); + const deleted = new Set(['foo']); + database.fireDidChangeItemsExternal({ deleted }); ok(changes.has('foo')); - equal(storage.get('foo', null!), null); + equal(storage.get('foo', undefined), undefined); changes.clear(); // Nothing happens if changing to same value - change.set('foo', undefined!); - database.fireDidChangeItemsExternal({ items: change }); + database.fireDidChangeItemsExternal({ deleted }); equal(changes.size, 0); await storage.close(); 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/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/browser/ui/tree/compressedObjectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts index b69e61abcb8..d13b997ea75 100644 --- a/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedObjectTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; -import { Iterator } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; @@ -16,7 +16,7 @@ interface IResolvedCompressedTreeElement extends ICompressedTreeElement { function resolve(treeElement: ICompressedTreeElement): IResolvedCompressedTreeElement { const result: any = { element: treeElement.element }; - const children = Iterator.collect(Iterator.map(Iterator.from(treeElement.children), resolve)); + const children = [...Iterable.map(Iterable.from(treeElement.children), resolve)]; if (treeElement.incompressible) { result.incompressible = true; @@ -315,25 +315,25 @@ suite('CompressedObjectTree', function () { const list: ITreeNode>[] = []; const model = new CompressedObjectTreeModel('test', toSpliceable(list)); - model.setChildren(null, Iterator.fromArray([ + model.setChildren(null, [ { element: 0 }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(toArray(list), [[0], [1], [2]]); assert.equal(model.size, 3); - model.setChildren(null, Iterator.fromArray([ + model.setChildren(null, [ { element: 3 }, { element: 4 }, { element: 5 }, - ])); + ]); assert.deepEqual(toArray(list), [[3], [4], [5]]); assert.equal(model.size, 3); - model.setChildren(null, Iterator.empty()); + model.setChildren(null); assert.deepEqual(toArray(list), []); assert.equal(model.size, 0); }); @@ -342,34 +342,34 @@ suite('CompressedObjectTree', function () { const list: ITreeNode>[] = []; const model = new CompressedObjectTreeModel('test', toSpliceable(list)); - model.setChildren(null, Iterator.fromArray([ + model.setChildren(null, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(toArray(list), [[0], [10], [11], [12], [1], [2]]); assert.equal(model.size, 6); - model.setChildren(12, Iterator.fromArray([ + model.setChildren(12, [ { element: 120 }, { element: 121 } - ])); + ]); assert.deepEqual(toArray(list), [[0], [10], [11], [12], [120], [121], [1], [2]]); assert.equal(model.size, 8); - model.setChildren(0, Iterator.empty()); + model.setChildren(0); assert.deepEqual(toArray(list), [[0], [1], [2]]); assert.equal(model.size, 3); - model.setChildren(null, Iterator.empty()); + model.setChildren(null); assert.deepEqual(toArray(list), []); assert.equal(model.size, 0); }); @@ -378,50 +378,50 @@ suite('CompressedObjectTree', function () { const list: ITreeNode>[] = []; const model = new CompressedObjectTreeModel('test', toSpliceable(list)); - model.setChildren(null, Iterator.fromArray([ + model.setChildren(null, [ { - element: 1, children: Iterator.fromArray([{ - element: 11, children: Iterator.fromArray([{ - element: 111, children: Iterator.fromArray([ + element: 1, children: [{ + element: 11, children: [{ + element: 111, children: [ { element: 1111 }, { element: 1112 }, { element: 1113 }, - ]) - }]) - }]) + ] + }] + }] } - ])); + ]); assert.deepEqual(toArray(list), [[1, 11, 111], [1111], [1112], [1113]]); assert.equal(model.size, 6); - model.setChildren(11, Iterator.fromArray([ + model.setChildren(11, [ { element: 111 }, { element: 112 }, { element: 113 }, - ])); + ]); assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113]]); assert.equal(model.size, 5); - model.setChildren(113, Iterator.fromArray([ + model.setChildren(113, [ { element: 1131 } - ])); + ]); assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131]]); assert.equal(model.size, 6); - model.setChildren(1131, Iterator.fromArray([ + model.setChildren(1131, [ { element: 1132 } - ])); + ]); assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131, 1132]]); assert.equal(model.size, 7); - model.setChildren(1131, Iterator.fromArray([ + model.setChildren(1131, [ { element: 1132 }, { element: 1133 }, - ])); + ]); assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131], [1132], [1133]]); assert.equal(model.size, 8); diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts index 43b8849801c..a5f46d06f2f 100644 --- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; -import { Iterator } from 'vs/base/common/iterator'; import { IndexTreeModel, IIndexTreeNode } from 'vs/base/browser/ui/tree/indexTreeModel'; function toSpliceable(arr: T[]): ISpliceable { @@ -34,11 +33,11 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 0 }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 3); assert.deepEqual(list[0].element, 0); @@ -56,17 +55,17 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 6); assert.deepEqual(list[0].element, 0); @@ -93,17 +92,17 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, collapsed: true, children: Iterator.fromArray([ + element: 0, collapsed: true, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 3); assert.deepEqual(list[0].element, 0); @@ -121,11 +120,11 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 0 }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 3); @@ -146,17 +145,17 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 6); @@ -180,17 +179,17 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 6); @@ -208,17 +207,17 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, collapsed: true, children: Iterator.fromArray([ + element: 0, collapsed: true, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 3); @@ -233,17 +232,17 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 6); @@ -264,17 +263,17 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, collapsed: true, children: Iterator.fromArray([ + element: 0, collapsed: true, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(list.length, 3); @@ -304,7 +303,7 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 1, children: [ { @@ -319,7 +318,7 @@ suite('IndexTreeModel', function () { { element: 21 } ] } - ])); + ]); assert.deepEqual(list.length, 5); assert.deepEqual(toArray(list), [1, 11, 111, 2, 21]); @@ -337,13 +336,13 @@ suite('IndexTreeModel', function () { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 } - ]) + ] } - ])); + ]); assert.deepEqual(list.length, 2); @@ -406,7 +405,7 @@ suite('IndexTreeModel', function () { const model = new IndexTreeModel('test', toSpliceable(list), -1, { filter }); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 0, children: [ { element: 1 }, @@ -418,7 +417,7 @@ suite('IndexTreeModel', function () { { element: 7 } ] } - ])); + ]); assert.deepEqual(list.length, 4); assert.deepEqual(toArray(list), [0, 2, 4, 6]); @@ -440,14 +439,14 @@ suite('IndexTreeModel', function () { const model = new IndexTreeModel('test', toSpliceable(list), -1, { filter }); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 0, children: [ { element: 1 }, { element: 2 } ] } - ])); + ]); assert.deepEqual(toArray(list), []); }); @@ -463,7 +462,7 @@ suite('IndexTreeModel', function () { const model = new IndexTreeModel('test', toSpliceable(list), -1, { filter }); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 0, children: [ { element: 1 }, @@ -475,7 +474,7 @@ suite('IndexTreeModel', function () { { element: 7 } ] }, - ])); + ]); assert.deepEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]); @@ -502,7 +501,7 @@ suite('IndexTreeModel', function () { const model = new IndexTreeModel('test', toSpliceable(list), 'root', { filter }); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 'vscode', children: [ { element: '.build' }, @@ -522,7 +521,7 @@ suite('IndexTreeModel', function () { } ] }, - ])); + ]); assert.deepEqual(list.length, 10); @@ -548,7 +547,7 @@ suite('IndexTreeModel', function () { const model = new IndexTreeModel('test', toSpliceable(list), 'root', { filter }); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 'vscode', children: [ { element: '.build' }, @@ -568,7 +567,7 @@ suite('IndexTreeModel', function () { } ] }, - ])); + ]); assert.deepEqual(list.length, 10); @@ -594,7 +593,7 @@ suite('IndexTreeModel', function () { const model = new IndexTreeModel('test', toSpliceable(list), 'root', { filter }); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 'vscode', collapsed: true, children: [ { element: '.build' }, @@ -614,7 +613,7 @@ suite('IndexTreeModel', function () { } ] }, - ])); + ]); assert.deepEqual(toArray(list), ['vscode']); @@ -642,17 +641,17 @@ suite('IndexTreeModel', function () { const list: IIndexTreeNode[] = []; const model = new IndexTreeModel('test', toSpliceable(list), -1); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(model.getNodeLocation(list[0]), [0]); assert.deepEqual(model.getNodeLocation(list[1]), [0, 0]); @@ -672,7 +671,7 @@ suite('IndexTreeModel', function () { const model = new IndexTreeModel('test', toSpliceable(list), -1, { filter }); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 0, children: [ { element: 1 }, @@ -684,7 +683,7 @@ suite('IndexTreeModel', function () { { element: 7 } ] } - ])); + ]); assert.deepEqual(model.getNodeLocation(list[0]), [0]); assert.deepEqual(model.getNodeLocation(list[1]), [0, 1]); @@ -704,11 +703,11 @@ suite('IndexTreeModel', function () { const model = new IndexTreeModel('test', toSpliceable(list), 'root', { filter }); - model.splice([0], 0, Iterator.fromArray([ + model.splice([0], 0, [ { element: 'silver' }, { element: 'gold' }, { element: 'platinum' } - ])); + ]); assert.deepEqual(toArray(list), ['silver', 'gold', 'platinum']); @@ -716,11 +715,11 @@ suite('IndexTreeModel', function () { model.refilter(); assert.deepEqual(toArray(list), ['platinum']); - model.splice([0], Number.POSITIVE_INFINITY, Iterator.fromArray([ + model.splice([0], Number.POSITIVE_INFINITY, [ { element: 'silver' }, { element: 'gold' }, { element: 'platinum' } - ])); + ]); assert.deepEqual(toArray(list), ['platinum']); model.refilter(); diff --git a/src/vs/base/test/browser/ui/tree/objectTree.test.ts b/src/vs/base/test/browser/ui/tree/objectTree.test.ts index d384c368d6e..166a99c00b3 100644 --- a/src/vs/base/test/browser/ui/tree/objectTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTree.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ObjectTree, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; -import { Iterator } from 'vs/base/common/iterator'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; suite('ObjectTree', function () { @@ -46,17 +45,17 @@ suite('ObjectTree', function () { }); test('should be able to navigate', () => { - tree.setChildren(null, Iterator.fromArray([ + tree.setChildren(null, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); const navigator = tree.navigate(); @@ -87,17 +86,17 @@ suite('ObjectTree', function () { }); test('should skip collapsed nodes', () => { - tree.setChildren(null, Iterator.fromArray([ + tree.setChildren(null, [ { - element: 0, collapsed: true, children: Iterator.fromArray([ + element: 0, collapsed: true, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); const navigator = tree.navigate(); @@ -118,17 +117,17 @@ suite('ObjectTree', function () { test('should skip filtered elements', () => { filter = el => el % 2 === 0; - tree.setChildren(null, Iterator.fromArray([ + tree.setChildren(null, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); const navigator = tree.navigate(); @@ -150,17 +149,17 @@ suite('ObjectTree', function () { }); test('should be able to start from node', () => { - tree.setChildren(null, Iterator.fromArray([ + tree.setChildren(null, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); const navigator = tree.navigate(1); @@ -291,50 +290,50 @@ suite('CompressibleObjectTree', function () { const tree = new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()]); tree.layout(200); - tree.setChildren(null, Iterator.fromArray([ + tree.setChildren(null, [ { - element: 1, children: Iterator.fromArray([{ - element: 11, children: Iterator.fromArray([{ - element: 111, children: Iterator.fromArray([ + element: 1, children: [{ + element: 11, children: [{ + element: 111, children: [ { element: 1111 }, { element: 1112 }, { element: 1113 }, - ]) - }]) - }]) + ] + }] + }] } - ])); + ]); let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent); assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']); - tree.setChildren(11, Iterator.fromArray([ + tree.setChildren(11, [ { element: 111 }, { element: 112 }, { element: 113 }, - ])); + ]); rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent); assert.deepEqual(rows, ['1/11', '111', '112', '113']); - tree.setChildren(113, Iterator.fromArray([ + tree.setChildren(113, [ { element: 1131 } - ])); + ]); rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent); assert.deepEqual(rows, ['1/11', '111', '112', '113/1131']); - tree.setChildren(1131, Iterator.fromArray([ + tree.setChildren(1131, [ { element: 1132 } - ])); + ]); rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent); assert.deepEqual(rows, ['1/11', '111', '112', '113/1131/1132']); - tree.setChildren(1131, Iterator.fromArray([ + tree.setChildren(1131, [ { element: 1132 }, { element: 1133 }, - ])); + ]); rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent); assert.deepEqual(rows, ['1/11', '111', '112', '113/1131', '1132', '1133']); @@ -348,19 +347,19 @@ suite('CompressibleObjectTree', function () { const tree = new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()]); tree.layout(200); - tree.setChildren(null, Iterator.fromArray([ + tree.setChildren(null, [ { - element: 1, children: Iterator.fromArray([{ - element: 11, children: Iterator.fromArray([{ - element: 111, children: Iterator.fromArray([ + element: 1, children: [{ + element: 11, children: [{ + element: 111, children: [ { element: 1111 }, { element: 1112 }, { element: 1113 }, - ]) - }]) - }]) + ] + }] + }] } - ])); + ]); let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent); assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']); diff --git a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts index 66ecd17238f..3f658685fce 100644 --- a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; -import { Iterator } from 'vs/base/common/iterator'; function toSpliceable(arr: T[]): ISpliceable { return { @@ -35,25 +34,25 @@ suite('ObjectTreeModel', function () { const list: ITreeNode[] = []; const model = new ObjectTreeModel('test', toSpliceable(list)); - model.setChildren(null, Iterator.fromArray([ + model.setChildren(null, [ { element: 0 }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(toArray(list), [0, 1, 2]); assert.equal(model.size, 3); - model.setChildren(null, Iterator.fromArray([ + model.setChildren(null, [ { element: 3 }, { element: 4 }, { element: 5 }, - ])); + ]); assert.deepEqual(toArray(list), [3, 4, 5]); assert.equal(model.size, 3); - model.setChildren(null, Iterator.empty()); + model.setChildren(null); assert.deepEqual(toArray(list), []); assert.equal(model.size, 0); }); @@ -62,34 +61,34 @@ suite('ObjectTreeModel', function () { const list: ITreeNode[] = []; const model = new ObjectTreeModel('test', toSpliceable(list)); - model.setChildren(null, Iterator.fromArray([ + model.setChildren(null, [ { - element: 0, children: Iterator.fromArray([ + element: 0, children: [ { element: 10 }, { element: 11 }, { element: 12 }, - ]) + ] }, { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(toArray(list), [0, 10, 11, 12, 1, 2]); assert.equal(model.size, 6); - model.setChildren(12, Iterator.fromArray([ + model.setChildren(12, [ { element: 120 }, { element: 121 } - ])); + ]); assert.deepEqual(toArray(list), [0, 10, 11, 12, 120, 121, 1, 2]); assert.equal(model.size, 8); - model.setChildren(0, Iterator.empty()); + model.setChildren(0); assert.deepEqual(toArray(list), [0, 1, 2]); assert.equal(model.size, 3); - model.setChildren(null, Iterator.empty()); + model.setChildren(null); assert.deepEqual(toArray(list), []); assert.equal(model.size, 0); }); @@ -98,16 +97,16 @@ suite('ObjectTreeModel', function () { const list: ITreeNode[] = []; const model = new ObjectTreeModel('test', toSpliceable(list)); - model.setChildren(null, Iterator.fromArray([ + model.setChildren(null, [ { element: 0, collapsed: true } - ])); + ]); assert.deepEqual(toArray(list), [0]); - model.setChildren(0, Iterator.fromArray([ + model.setChildren(0, [ { element: 1 }, { element: 2 } - ])); + ]); assert.deepEqual(toArray(list), [0]); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index dda98ab6e1c..25e7d83cc7b 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import * as async from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; suite('Async', () => { @@ -646,4 +647,45 @@ suite('Async', () => { assert.ok(pendingCancelled); }); + + test('raceCancellation', async () => { + const cts = new CancellationTokenSource(); + + const now = Date.now(); + + const p = async.raceCancellation(async.timeout(100), cts.token); + cts.cancel(); + + await p; + + assert.ok(Date.now() - now < 100); + }); + + test('raceTimeout', async () => { + const cts = new CancellationTokenSource(); + + // timeout wins + let now = Date.now(); + let timedout = false; + + const p1 = async.raceTimeout(async.timeout(100), 1, () => timedout = true); + cts.cancel(); + + await p1; + + assert.ok(Date.now() - now < 100); + assert.equal(timedout, true); + + // promise wins + now = Date.now(); + timedout = false; + + const p2 = async.raceTimeout(async.timeout(1), 100, () => timedout = true); + cts.cancel(); + + await p2; + + assert.ok(Date.now() - now < 100); + assert.equal(timedout, false); + }); }); 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/test/common/extpath.test.ts b/src/vs/base/test/common/extpath.test.ts index eb3d8da7a46..02aa3a96377 100644 --- a/src/vs/base/test/common/extpath.test.ts +++ b/src/vs/base/test/common/extpath.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import * as extpath from 'vs/base/common/extpath'; import * as platform from 'vs/base/common/platform'; +import { CharCode } from 'vs/base/common/charCode'; suite('Paths', () => { @@ -114,4 +115,19 @@ suite('Paths', () => { assert.ok(!extpath.isRootOrDriveLetter('/path')); } }); + + test('isWindowsDriveLetter', () => { + assert.ok(!extpath.isWindowsDriveLetter(0)); + assert.ok(!extpath.isWindowsDriveLetter(-1)); + assert.ok(extpath.isWindowsDriveLetter(CharCode.A)); + assert.ok(extpath.isWindowsDriveLetter(CharCode.z)); + }); + + test('indexOfPath', () => { + assert.equal(extpath.indexOfPath('/foo', '/bar', true), -1); + assert.equal(extpath.indexOfPath('/foo', '/FOO', false), -1); + assert.equal(extpath.indexOfPath('/foo', '/FOO', true), 0); + assert.equal(extpath.indexOfPath('/some/long/path', '/some/long', false), 0); + assert.equal(extpath.indexOfPath('/some/long/path', '/PATH', true), 10); + }); }); diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index 0cf70ae8555..ffbe5c89c87 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -343,6 +343,36 @@ suite('Filters', () => { ); }); + test('Freeze when fjfj -> jfjf, https://github.com/microsoft/vscode/issues/91807', function () { + assertMatches( + 'jfjfj', + 'fjfjfjfjfjfjfjfjfjfjfj', + undefined, fuzzyScore + ); + assertMatches( + 'jfjfjfjfjfjfjfjfjfj', + 'fjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj', + undefined, fuzzyScore + ); + assertMatches( + 'jfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfj', + 'fjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj', + undefined, fuzzyScore + ); + assertMatches( + 'jfjfjfjfjfjfjfjfjfj', + 'fJfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj', + 'f^J^f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^jfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj', // strong match + fuzzyScore + ); + assertMatches( + 'jfjfjfjfjfjfjfjfjfj', + 'fjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj', + 'f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^jfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj', // any match + fuzzyScore, { firstMatchCanBeWeak: true } + ); + }); + test('fuzzyScore, issue #26423', function () { assertMatches('baba', 'abababab', undefined, fuzzyScore); 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..7f32bc3eb6a 100644 --- a/src/vs/base/test/common/iterator.test.ts +++ b/src/vs/base/test/common/iterator.test.ts @@ -4,16 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Iterator } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; -suite('Iterator', () => { - test('concat', () => { - const first = Iterator.fromArray([1, 2, 3]); - const second = Iterator.fromArray([4, 5, 6]); - const third = Iterator.fromArray([7, 8, 9]); - const actualIterator = Iterator.concat(first, second, third); - const actual = Iterator.collect(actualIterator); +suite('Iterable', function () { - assert.deepEqual(actual, [1, 2, 3, 4, 5, 6, 7, 8, 9]); + 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 }); -}); \ No newline at end of file + +}); 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/browser/linkedText.test.ts b/src/vs/base/test/common/linkedText.test.ts similarity index 60% rename from src/vs/base/test/browser/linkedText.test.ts rename to src/vs/base/test/common/linkedText.test.ts index 2c765555ffe..a7b61a558c2 100644 --- a/src/vs/base/test/browser/linkedText.test.ts +++ b/src/vs/base/test/common/linkedText.test.ts @@ -4,50 +4,65 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { parseLinkedText } from 'vs/base/browser/linkedText'; +import { parseLinkedText } from 'vs/base/common/linkedText'; suite('LinkedText', () => { test('parses correctly', () => { - assert.deepEqual(parseLinkedText(''), []); - assert.deepEqual(parseLinkedText('hello'), ['hello']); - assert.deepEqual(parseLinkedText('hello there'), ['hello there']); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href).'), [ + assert.deepEqual(parseLinkedText('').nodes, []); + assert.deepEqual(parseLinkedText('hello').nodes, ['hello']); + assert.deepEqual(parseLinkedText('hello there').nodes, ['hello there']); + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href).').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").'), [ + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").').nodes, [ 'Some message with ', { label: 'link text', href: 'http://link.href', title: 'and a title' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).'), [ + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href \'and a title\').').nodes, [ + 'Some message with ', + { label: 'link text', href: 'http://link.href', title: 'and a title' }, + '.' + ]); + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a \'title\'").').nodes, [ + 'Some message with ', + { label: 'link text', href: 'http://link.href', title: 'and a \'title\'' }, + '.' + ]); + assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href \'and a "title"\').').nodes, [ + 'Some message with ', + { label: 'link text', href: 'http://link.href', title: 'and a "title"' }, + '.' + ]); + assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).').nodes, [ 'Some message with [link text](random stuff).' ]); - assert.deepEqual(parseLinkedText('Some message with [https link](https://link.href).'), [ + assert.deepEqual(parseLinkedText('Some message with [https link](https://link.href).').nodes, [ 'Some message with ', { label: 'https link', href: 'https://link.href' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [https link](https:).'), [ + assert.deepEqual(parseLinkedText('Some message with [https link](https:).').nodes, [ 'Some message with [https link](https:).' ]); - assert.deepEqual(parseLinkedText('Some message with [a command](command:foobar).'), [ + assert.deepEqual(parseLinkedText('Some message with [a command](command:foobar).').nodes, [ 'Some message with ', { label: 'a command', href: 'command:foobar' }, '.' ]); - assert.deepEqual(parseLinkedText('Some message with [a command](command:).'), [ + assert.deepEqual(parseLinkedText('Some message with [a command](command:).').nodes, [ 'Some message with [a command](command:).' ]); - assert.deepEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...'), [ + assert.deepEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...').nodes, [ 'link ', { label: 'one', href: 'command:foo', title: 'nice' }, ' and link ', { label: 'two', href: 'http://foo' }, '...' ]); - assert.deepEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...'), [ + assert.deepEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...').nodes, [ 'link\n', { label: 'one', href: 'command:foo', title: 'nice' }, '\nand link ', diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 8afd0496f24..737ecf0138a 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, UriIterator } 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', () => { @@ -313,7 +312,64 @@ suite('Map', () => { assert.equal(iter.hasNext(), false); }); - function assertTernarySearchTree(trie: TernarySearchTree, ...elements: [string, E][]) { + test('URIIterator', function () { + const iter = new UriIterator(); + iter.reset(URI.parse('file:///usr/bin/file.txt')); + + assert.equal(iter.value(), 'file'); + assert.equal(iter.cmp('FILE'), 0); + assert.equal(iter.hasNext(), true); + iter.next(); + + assert.equal(iter.value(), 'usr'); + assert.equal(iter.hasNext(), true); + iter.next(); + + assert.equal(iter.value(), 'bin'); + assert.equal(iter.hasNext(), true); + iter.next(); + + assert.equal(iter.value(), 'file.txt'); + assert.equal(iter.hasNext(), false); + + + iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); + + // scheme + assert.equal(iter.value(), 'file'); + assert.equal(iter.cmp('FILE'), 0); + assert.equal(iter.hasNext(), true); + iter.next(); + + // authority + assert.equal(iter.value(), 'share'); + assert.equal(iter.cmp('SHARe'), 0); + assert.equal(iter.hasNext(), true); + iter.next(); + + // path + assert.equal(iter.value(), 'usr'); + assert.equal(iter.hasNext(), true); + iter.next(); + + // path + assert.equal(iter.value(), 'bin'); + assert.equal(iter.hasNext(), true); + iter.next(); + + // path + assert.equal(iter.value(), 'file.txt'); + assert.equal(iter.hasNext(), true); + iter.next(); + + // query + assert.equal(iter.value(), 'foo'); + assert.equal(iter.cmp('z') > 0, true); + assert.equal(iter.cmp('a') < 0, true); + assert.equal(iter.hasNext(), false); + }); + + function assertTernarySearchTree(trie: TernarySearchTree, ...elements: [string, E][]) { const map = new Map(); for (const [key, value] of elements) { map.set(key, value); @@ -379,7 +435,7 @@ suite('Map', () => { }); test('TernarySearchTree - basics', function () { - let trie = new TernarySearchTree(new StringIterator()); + let trie = new TernarySearchTree(new StringIterator()); trie.set('foo', 1); trie.set('bar', 2); @@ -409,7 +465,7 @@ suite('Map', () => { }); test('TernarySearchTree - delete & cleanup', function () { - let trie = new TernarySearchTree(new StringIterator()); + let trie = new TernarySearchTree(new StringIterator()); trie.set('foo', 1); trie.set('foobar', 2); trie.set('bar', 3); @@ -419,7 +475,7 @@ suite('Map', () => { }); test('TernarySearchTree (PathSegments) - basics', function () { - let trie = new TernarySearchTree(new PathIterator()); + let trie = new TernarySearchTree(new PathIterator()); trie.set('/user/foo/bar', 1); trie.set('/user/foo', 2); @@ -443,7 +499,7 @@ suite('Map', () => { test('TernarySearchTree (PathSegments) - lookup', function () { - const map = new TernarySearchTree(new PathIterator()); + const map = new TernarySearchTree(new PathIterator()); map.set('/user/foo/bar', 1); map.set('/user/foo', 2); map.set('/user/foo/flip/flop', 3); @@ -457,7 +513,7 @@ suite('Map', () => { test('TernarySearchTree (PathSegments) - superstr', function () { - const map = new TernarySearchTree(new PathIterator()); + const map = new TernarySearchTree(new PathIterator()); map.set('/user/foo/bar', 1); map.set('/user/foo', 2); map.set('/user/foo/flip/flop', 3); @@ -494,6 +550,100 @@ suite('Map', () => { assert.equal(map.findSuperstr('/userr'), undefined); }); + + test('TernarySearchTree (URI) - basics', function () { + let trie = new TernarySearchTree(new UriIterator()); + + trie.set(URI.file('/user/foo/bar'), 1); + trie.set(URI.file('/user/foo'), 2); + trie.set(URI.file('/user/foo/flip/flop'), 3); + + assert.equal(trie.get(URI.file('/user/foo/bar')), 1); + assert.equal(trie.get(URI.file('/user/foo')), 2); + assert.equal(trie.get(URI.file('/user/foo/flip/flop')), 3); + + assert.equal(trie.findSubstr(URI.file('/user/bar')), undefined); + assert.equal(trie.findSubstr(URI.file('/user/foo')), 2); + assert.equal(trie.findSubstr(URI.file('/user/foo/ba')), 2); + assert.equal(trie.findSubstr(URI.file('/user/foo/far/boo')), 2); + assert.equal(trie.findSubstr(URI.file('/user/foo/bar')), 1); + assert.equal(trie.findSubstr(URI.file('/user/foo/bar/far/boo')), 1); + }); + + test('TernarySearchTree (URI) - lookup', function () { + + const map = new TernarySearchTree(new UriIterator()); + map.set(URI.parse('http://foo.bar/user/foo/bar'), 1); + map.set(URI.parse('http://foo.bar/user/foo?query'), 2); + map.set(URI.parse('http://foo.bar/user/foo?QUERY'), 3); + map.set(URI.parse('http://foo.bar/user/foo/flip/flop'), 3); + + assert.equal(map.get(URI.parse('http://foo.bar/foo')), undefined); + assert.equal(map.get(URI.parse('http://foo.bar/user')), undefined); + assert.equal(map.get(URI.parse('http://foo.bar/user/foo/bar')), 1); + assert.equal(map.get(URI.parse('http://foo.bar/user/foo?query')), 2); + assert.equal(map.get(URI.parse('http://foo.bar/user/foo?Query')), undefined); + assert.equal(map.get(URI.parse('http://foo.bar/user/foo?QUERY')), 3); + assert.equal(map.get(URI.parse('http://foo.bar/user/foo/bar/boo')), undefined); + }); + + test('TernarySearchTree (PathSegments) - superstr', function () { + + const map = new TernarySearchTree(new UriIterator()); + map.set(URI.file('/user/foo/bar'), 1); + map.set(URI.file('/user/foo'), 2); + map.set(URI.file('/user/foo/flip/flop'), 3); + map.set(URI.file('/usr/foo'), 4); + + let item: IteratorResult; + let iter = map.findSuperstr(URI.file('/user'))!; + + item = iter.next(); + assert.equal(item.value, 2); + assert.equal(item.done, false); + item = iter.next(); + assert.equal(item.value, 1); + assert.equal(item.done, false); + item = iter.next(); + assert.equal(item.value, 3); + assert.equal(item.done, false); + item = iter.next(); + assert.equal(item.value, undefined); + assert.equal(item.done, true); + + iter = map.findSuperstr(URI.file('/usr'))!; + item = iter.next(); + assert.equal(item.value, 4); + assert.equal(item.done, false); + + item = iter.next(); + assert.equal(item.value, undefined); + assert.equal(item.done, true); + + iter = map.findSuperstr(URI.file('/'))!; + item = iter.next(); + assert.equal(item.value, 2); + assert.equal(item.done, false); + item = iter.next(); + assert.equal(item.value, 1); + assert.equal(item.done, false); + item = iter.next(); + assert.equal(item.value, 3); + assert.equal(item.done, false); + item = iter.next(); + assert.equal(item.value, 4); + assert.equal(item.done, false); + item = iter.next(); + assert.equal(item.value, undefined); + assert.equal(item.done, true); + + assert.equal(map.findSuperstr(URI.file('/not')), undefined); + assert.equal(map.findSuperstr(URI.file('/us')), undefined); + assert.equal(map.findSuperstr(URI.file('/usrr')), undefined); + assert.equal(map.findSuperstr(URI.file('/userr')), undefined); + }); + + test('ResourceMap - basics', function () { const map = new ResourceMap(); @@ -630,17 +780,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..bce692cd6c9 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -81,6 +81,27 @@ suite('Strings', () => { assertCompareIgnoreCase('O', '/'); }); + test('compareIgnoreCase (substring)', () => { + + function assertCompareIgnoreCase(a: string, b: string, aStart: number, aEnd: number, bStart: number, bEnd: number, recurse = true): void { + let actual = strings.compareSubstringIgnoreCase(a, b, aStart, aEnd, bStart, bEnd); + actual = actual > 0 ? 1 : actual < 0 ? -1 : actual; + + let expected = strings.compare(a.toLowerCase().substring(aStart, aEnd), b.toLowerCase().substring(bStart, bEnd)); + expected = expected > 0 ? 1 : expected < 0 ? -1 : expected; + assert.equal(actual, expected, `${a} <> ${b}`); + + if (recurse) { + assertCompareIgnoreCase(b, a, bStart, bEnd, aStart, aEnd, false); + } + } + + assertCompareIgnoreCase('', '', 0, 0, 0, 0); + assertCompareIgnoreCase('abc', 'ABC', 0, 1, 0, 1); + assertCompareIgnoreCase('abc', 'Aabc', 0, 3, 1, 4); + assertCompareIgnoreCase('abcABc', 'ABcd', 3, 6, 0, 4); + }); + test('format', () => { assert.strictEqual(strings.format('Foo Bar'), 'Foo Bar'); assert.strictEqual(strings.format('Foo {0} Bar'), 'Foo {0} Bar'); @@ -92,15 +113,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 +416,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..bc4e409c02d 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, Firefox, Safari => foo://bar/bazz + }); + + 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', false); // Firefox -> Different, Edge, Chrome, Safar -> OK + assertJoined(('file://ser/foo'), '../../bazz', 'file://ser/bazz', false); // Firefox -> Different, Edge, Chrome, Safar -> OK + }); + + 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/encoding/encoding.test.ts b/src/vs/base/test/node/encoding/encoding.test.ts index 1931c76aa40..024f7084d72 100644 --- a/src/vs/base/test/node/encoding/encoding.test.ts +++ b/src/vs/base/test/node/encoding/encoding.test.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; import * as encoding from 'vs/base/node/encoding'; import * as terminalEncoding from 'vs/base/node/terminalEncoding'; import { Readable } from 'stream'; +import * as iconv from 'iconv-lite'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export async function detectEncodingByBOM(file: string): Promise { @@ -224,7 +225,7 @@ suite('Encoding', () => { if (err) { reject(err); } else { - resolve(encoding.decode(data, fileEncoding!)); + resolve(iconv.decode(data, encoding.toNodeEncoding(fileEncoding!))); } }); }); 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..556c03a03ab 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(); @@ -326,7 +322,11 @@ class WorkspaceProvider implements IWorkspaceProvider { // Payload case WorkspaceProvider.QUERY_PARAM_PAYLOAD: - payload = JSON.parse(value); + try { + payload = JSON.parse(value); + } catch (error) { + console.error(error); // possible invalid JSON + } break; } }); @@ -342,30 +342,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 f47161fb8bf..a517bafe66b 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -17,14 +17,13 @@ import { escape } from 'vs/base/common/strings'; import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; -import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil'; +import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; import 'vs/css!./media/issueReporter'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; @@ -39,7 +38,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 +48,7 @@ interface SearchResult { state?: string; } -export interface IssueReporterConfiguration extends IWindowConfiguration { +export interface IssueReporterConfiguration extends INativeWindowConfiguration { data: IssueReporterData; features: IssueReporterFeatures; } @@ -63,7 +62,7 @@ export function startup(configuration: IssueReporterConfiguration) { } export class IssueReporter extends Disposable { - private environmentService!: IEnvironmentService; + private environmentService!: INativeEnvironmentService; private telemetryService!: ITelemetryService; private logService!: ILogService; private readonly issueReporterModel: IssueReporterModel; @@ -81,6 +80,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 +89,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'); @@ -97,6 +98,23 @@ export class IssueReporter extends Disposable { this.previewButton = new Button(issueReporterElement); } + const issueTitle = configuration.data.issueTitle; + if (issueTitle) { + const issueTitleElement = this.getElementById('issue-title'); + if (issueTitleElement) { + issueTitleElement.value = issueTitle; + } + } + + const issueBody = configuration.data.issueBody; + if (issueBody) { + const description = this.getElementById('description'); + if (description) { + description.value = issueBody; + this.issueReporterModel.update({ issueDescription: issueBody }); + } + } + ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_: unknown, info: Partial) => { this.logService.trace('issueReporter: Received performance data'); this.issueReporterModel.update(info); @@ -243,19 +261,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 { @@ -299,7 +318,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); @@ -696,7 +715,7 @@ export class IssueReporter extends Disposable { type IssueReporterSearchError = { message: string; }; - this.telemetryService.publicLog2('issueReporterSearchError', { message: error.message }); + this.telemetryService.publicLog2('issueReporterSearchError', { message: error.message }, true); } private setUpTypes(): void { @@ -731,10 +750,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 = ''; @@ -1175,8 +1198,8 @@ export class IssueReporter extends Disposable { } } - private getElementById(elementId: string): HTMLElement | undefined { - const element = document.getElementById(elementId); + private getElementById(elementId: string): T | undefined { + const element = document.getElementById(elementId) as T | undefined; if (element) { return element; } else { diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-browser/issue/issueReporterModel.ts index 6c7fa528e37..96bae9110ee 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-browser/issue/issueReporterModel.ts @@ -168,6 +168,13 @@ ${this.getInfos()} |Screen Reader|${this._data.systemInfo.screenReader}| |VM|${this._data.systemInfo.vmHint}|`; + if (this._data.systemInfo.linuxEnv) { + md += `\n|DESKTOP_SESSION|${this._data.systemInfo.linuxEnv.desktopSession}| +|XDG_CURRENT_DESKTOP|${this._data.systemInfo.linuxEnv.xdgCurrentDesktop}| +|XDG_SESSION_DESKTOP|${this._data.systemInfo.linuxEnv.xdgSessionDesktop}| +|XDG_SESSION_TYPE|${this._data.systemInfo.linuxEnv.xdgSessionType}|`; + } + this._data.systemInfo.remoteData.forEach(remote => { if (isRemoteDiagnosticError(remote)) { md += `\n\n${remote.errorMessage}`; @@ -268,4 +275,4 @@ ${table} `; } -} \ No newline at end of file +} diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts index b34917961c0..2dda07e6fed 100644 --- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; -import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil'; +import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; import { IssueType } from 'vs/platform/issue/node/issue'; suite('IssueReporter', () => { @@ -81,6 +81,55 @@ OS version: undefined `); }); + test('serializes Linux environment information when data is provided', () => { + const issueReporterModel = new IssueReporterModel({ + issueType: 0, + systemInfo: { + os: 'Darwin', + cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)', + memory: '16.00GB', + vmHint: '0%', + processArgs: '', + screenReader: 'no', + remoteData: [], + gpuStatus: {}, + linuxEnv: { + desktopSession: 'ubuntu', + xdgCurrentDesktop: 'ubuntu', + xdgSessionDesktop: 'ubuntu:GNOME', + xdgSessionType: 'x11' + } + } + }); + assert.equal(issueReporterModel.serialize(), + ` +Issue Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined + +
+System Info + +|Item|Value| +|---|---| +|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)| +|GPU Status|| +|Load (avg)|undefined| +|Memory (System)|16.00GB| +|Process Argv|| +|Screen Reader|no| +|VM|0%| +|DESKTOP_SESSION|ubuntu| +|XDG_CURRENT_DESKTOP|ubuntu| +|XDG_SESSION_DESKTOP|ubuntu:GNOME| +|XDG_SESSION_TYPE|x11| +
Extensions: none +`); + }); + test('serializes remote information when data is provided', () => { const issueReporterModel = new IssueReporterModel({ issueType: 0, diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts index 09ccca9b823..9684cbd4c20 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts @@ -75,7 +75,11 @@ function getProcessItem(processes: FormattedProcessItem[], item: ProcessItem, in // Recurse into children if any if (Array.isArray(item.children)) { - item.children.forEach(child => getProcessItem(processes, child, indent + 1, isLocal)); + item.children.forEach(child => { + if (child) { + getProcessItem(processes, child, indent + 1, isLocal); + } + }); } } diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index 0e79bc81179..a1fbdef709f 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -5,12 +5,12 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; - import { IStringDictionary } from 'vs/base/common/collections'; import product from 'vs/platform/product/common/product'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; interface ExtensionEntry { @@ -33,7 +33,7 @@ interface LanguagePackFile { export class LanguagePackCachedDataCleaner extends Disposable { constructor( - @IEnvironmentService private readonly _environmentService: IEnvironmentService, + @IEnvironmentService private readonly _environmentService: INativeEnvironmentService, @ILogService private readonly _logService: ILogService ) { super(); @@ -102,4 +102,4 @@ export class LanguagePackCachedDataCleaner extends Disposable { } })); } -} \ No newline at end of file +} diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts index c3a758470fc..a3d77fb7921 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts @@ -8,6 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { readdir, rimraf, stat } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import product from 'vs/platform/product/common/product'; export class NodeCachedDataCleaner { @@ -19,7 +20,7 @@ export class NodeCachedDataCleaner { private readonly _disposables = new DisposableStore(); constructor( - @IEnvironmentService private readonly _environmentService: IEnvironmentService + @IEnvironmentService private readonly _environmentService: INativeEnvironmentService ) { this._manageCachedDataSoon(); } diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index a3899b854fb..7672ec3c7d8 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { join } from 'vs/base/common/path'; import { readdir, readFile, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -16,7 +17,7 @@ export class StorageDataCleaner extends Disposable { private static readonly NON_EMPTY_WORKSPACE_ID_LENGTH = 128 / 4; constructor( - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: INativeEnvironmentService ) { super(); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 9e2970756db..47f26569b0b 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -10,14 +10,15 @@ import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/browser/requestService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -49,23 +50,26 @@ 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, IUserDataAuthTokenService, 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, UserDataAuthTokenServiceChannel, 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 { UserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataAuthTokenService'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; 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'; +import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -139,6 +143,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); @@ -156,14 +162,13 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat let telemetryService: ITelemetryService; instantiationService.invokeFunction(accessor => { const services = new ServiceCollection(); - const environmentService = accessor.get(IEnvironmentService); const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; const telemetryLogService = new FollowerLogService(loggerClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.'); telemetryLogService.info('==========================================================='); let appInsightsAppender: ITelemetryAppender | null = NullAppender; - if (!extensionDevelopmentLocationURI && !environmentService.args['disable-telemetry'] && product.enableTelemetry) { + if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) { if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) { appInsightsAppender = new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, telemetryLogService); disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data @@ -186,15 +191,16 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); + services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService)); services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService)); - services.set(IUserDataAuthTokenService, new SyncDescriptor(UserDataAuthTokenService)); + services.set(IAuthenticationTokenService, new SyncDescriptor(AuthenticationTokenService)); services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); 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(); @@ -214,13 +220,13 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService); server.registerChannel('diagnostics', diagnosticsChannel); - const authTokenService = accessor.get(IUserDataAuthTokenService); - const authTokenChannel = new UserDataAuthTokenServiceChannel(authTokenService); - server.registerChannel('authToken', authTokenChannel); + const extensionTipsService = accessor.get(IExtensionTipsService); + const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService); + server.registerChannel('extensionTipsService', extensionTipsChannel); - const settingsSyncService = accessor.get(ISettingsSyncService); - const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService); - server.registerChannel('settingsSync', settingsSyncChannel); + const authTokenService = accessor.get(IAuthenticationTokenService); + const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService); + server.registerChannel('authToken', authTokenChannel); const userDataSyncService = accessor.get(IUserDataSyncService); const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 7f2fced207b..ac970624ec8 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -6,7 +6,8 @@ import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent, BrowserWindow } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; -import { OpenContext, IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { OpenContext } from 'vs/platform/windows/node/window'; import { ActiveWindowManager } from 'vs/code/node/activeWindowTracker'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; @@ -24,9 +25,9 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; +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 +62,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'; @@ -73,16 +73,15 @@ import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsSer import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IElectronMainService, ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; -import { assign } from 'vs/base/common/objects'; import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; 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'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; 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; @@ -91,7 +90,7 @@ export class CodeApplication extends Disposable { private readonly userEnv: IProcessEnvironment, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStateService private readonly stateService: IStateService @@ -133,7 +132,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 +172,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 @@ -395,7 +394,7 @@ export class CodeApplication extends Disposable { const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient)); // Post Open Windows Tasks - appInstantiationService.invokeFunction(this.afterWindowOpen.bind(this)); + appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); // Tracing: Stop tracing after windows are ready if enabled if (this.environmentService.args.trace) { @@ -407,21 +406,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 +510,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,13 +570,17 @@ 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)); - const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); - // ExtensionHost Debug broadcast service + const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService)); // Signal phase: ready (services set) @@ -586,47 +589,67 @@ export class CodeApplication extends Disposable { // Propagate to clients this.dialogMainService = accessor.get(IDialogMainService); + // Check for initial URLs to handle from protocol link invocations + const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = []; + const pendingProtocolLinksToHandle = coalesce([ + + // Windows/Linux: protocol handler invokes CLI with --open-url + ...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [], + + // macOS: open-url events + ...((global).getOpenUrls() || []) as string[] + ].map(pendingUrlToHandle => { + try { + return URI.parse(pendingUrlToHandle); + } catch (error) { + return undefined; + } + })).filter(pendingUriToHandle => { + // filter out any protocol link that wants to open as window so that + // we open the right set of windows on startup and not restore the + // previous workspace too. + const windowOpenable = this.getWindowOpenableFromProtocolLink(pendingUriToHandle); + if (windowOpenable) { + pendingWindowOpenablesFromProtocolLinks.push(windowOpenable); + + return false; + } + + return true; + }); + // Create a URL handler to open file URIs in the active window - const environmentService = accessor.get(IEnvironmentService); + const app = this; + const environmentService = this.environmentService; urlService.registerHandler({ - async handleURL(uri: URI, options?: IOpenURLOptions): Promise { + async handleURL(uri: URI): Promise { - // Catch file/remote URLs - if ((uri.authority === Schemas.file || uri.authority === Schemas.vscodeRemote) && !!uri.path) { - const cli = assign(Object.create(null), environmentService.args); - const urisToOpen: IWindowOpenable[] = []; + // Check for URIs to open in window + const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri); + if (windowOpenableFromProtocolLink) { + windowsMainService.open({ + context: OpenContext.API, + cli: { ...environmentService.args }, + urisToOpen: [windowOpenableFromProtocolLink], + gotoLineMode: true + }); - // File path - if (uri.authority === Schemas.file) { - // we configure as fileUri, but later validation will - // make sure to open as folder or workspace if possible - urisToOpen.push({ fileUri: URI.file(uri.fsPath) }); - } + return true; + } - // Remote path - else { - // Example conversion: - // From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco - // To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco - const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */); - if (secondSlash !== -1) { - const authority = uri.path.substring(1, secondSlash); - const path = uri.path.substring(secondSlash); - const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment }); + // If we have not yet handled the URI and we have no window opened (macOS only) + // we first open a window and then try to open that URI within that window + if (isMacintosh && windowsMainService.getWindowCount() === 0) { + const [window] = windowsMainService.open({ + context: OpenContext.API, + cli: { ...environmentService.args }, + forceEmpty: true, + gotoLineMode: true + }); - if (hasWorkspaceFileExtension(path)) { - urisToOpen.push({ workspaceUri: remoteUri }); - } else { - urisToOpen.push({ folderUri: remoteUri }); - } - } - } + await window.ready(); - if (urisToOpen.length > 0) { - windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true }); - - return true; - } + return urlService.open(uri); } return false; @@ -638,37 +661,13 @@ export class CodeApplication extends Disposable { const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter); const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter); - const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel); - - // On Mac, Code can be running without any open windows, so we must create a window to handle urls, - // if there is none - if (isMacintosh) { - urlService.registerHandler({ - async handleURL(uri: URI, options?: IOpenURLOptions): Promise { - if (windowsMainService.getWindowCount() === 0) { - const cli = { ...environmentService.args }; - const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true, gotoLineMode: true }); - - await window.ready(); - - return urlService.open(uri); - } - - return false; - } - }); - } - - // Register the multiple URL handler - urlService.registerHandler(multiplexURLHandler); + urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel)); // Watch Electron URLs and forward them to the UrlService - const args = this.environmentService.args; - const urls = args['open-url'] ? args._urls : []; - const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService, this.environmentService); - this._register(urlListener); + this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentService)); // Open our first window + const args = this.environmentService.args; const macOpenFiles: string[] = (global).macOpenFiles; const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; const hasCliArgs = args._.length; @@ -677,8 +676,21 @@ export class CodeApplication extends Disposable { const noRecentEntry = args['skip-add-to-recently-opened'] === true; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; - // new window if "-n" was used without paths - if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { + // check for a pending window to open from URI + // e.g. when running code with --open-uri from + // a protocol handler + if (pendingWindowOpenablesFromProtocolLinks.length > 0) { + return windowsMainService.open({ + context, + cli: args, + urisToOpen: pendingWindowOpenablesFromProtocolLinks, + gotoLineMode: true, + initialStartup: true + }); + } + + // new window if "-n" or "--remote" was used without paths + if ((args['new-window'] || args.remote) && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { return windowsMainService.open({ context, cli: args, @@ -698,7 +710,6 @@ export class CodeApplication extends Disposable { urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)), noRecentEntry, waitMarkerFileURI, - gotoLineMode: false, initialStartup: true }); } @@ -716,6 +727,40 @@ export class CodeApplication extends Disposable { }); } + private getWindowOpenableFromProtocolLink(uri: URI): IWindowOpenable | undefined { + if (!uri.path) { + return undefined; + } + + // File path + if (uri.authority === Schemas.file) { + // we configure as fileUri, but later validation will + // make sure to open as folder or workspace if possible + return { fileUri: URI.file(uri.fsPath) }; + } + + // Remote path + else if (uri.authority === Schemas.vscodeRemote) { + // Example conversion: + // From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco + // To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco + const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */); + if (secondSlash !== -1) { + const authority = uri.path.substring(1, secondSlash); + const path = uri.path.substring(secondSlash); + const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment }); + + if (hasWorkspaceFileExtension(path)) { + return { workspaceUri: remoteUri }; + } else { + return { folderUri: remoteUri }; + } + } + } + + return undefined; + } + private getWindowOpenableFromPathSync(path: string): IWindowOpenable { try { const fileStat = statSync(path); @@ -734,6 +779,7 @@ export class CodeApplication extends Disposable { } private afterWindowOpen(accessor: ServicesAccessor): void { + // Signal phase: after window open this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; @@ -763,7 +809,7 @@ class ElectronExtensionHostDebugBroadcastChannel extends ExtensionHost super(); } - call(ctx: TContext, command: string, arg?: any): Promise { + async call(ctx: TContext, command: string, arg?: any): Promise { if (command === 'openExtensionDevelopmentHostWindow') { const env = arg[1]; const pargs = parseArgs(arg[0], OPTIONS); @@ -775,7 +821,6 @@ class ElectronExtensionHostDebugBroadcastChannel extends ExtensionHost userEnv: Object.keys(env).length > 0 ? env : undefined }); } - return Promise.resolve(); } else { return super.call(ctx, command, arg); } 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 1c098b9e8f0..27ecfe6f5b3 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -5,7 +5,6 @@ import 'vs/platform/update/common/update.config.contribution'; import { app, dialog } from 'electron'; -import { assign } from 'vs/base/common/objects'; import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import product from 'vs/platform/product/common/product'; import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; @@ -23,10 +22,11 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log'; import { StateService } from 'vs/platform/state/node/stateService'; import { IStateService } from 'vs/platform/state/node/state'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { EnvironmentService, xdgRuntimeDir } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; +import { EnvironmentService, xdgRuntimeDir, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService'; import * as fs from 'fs'; @@ -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; @@ -97,12 +98,11 @@ class CodeMain { // log file access on Windows (https://github.com/Microsoft/vscode/issues/41218) const bufferLogService = new BufferLogService(); - const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService); + const [instantiationService, instanceEnvironment, environmentService] = this.createServices(args, bufferLogService); try { // Init services await instantiationService.invokeFunction(async accessor => { - const environmentService = accessor.get(IEnvironmentService); const configurationService = accessor.get(IConfigurationService); const stateService = accessor.get(IStateService); @@ -119,13 +119,12 @@ class CodeMain { // Startup await instantiationService.invokeFunction(async accessor => { - const environmentService = accessor.get(IEnvironmentService); const logService = accessor.get(ILogService); const lifecycleMainService = accessor.get(ILifecycleMainService); const fileService = accessor.get(IFileService); const configurationService = accessor.get(IConfigurationService); - const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, true); + const mainIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true); bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel()); once(lifecycleMainService.onWillShutdown)(() => { @@ -140,7 +139,7 @@ class CodeMain { } } - private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment] { + private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, INativeEnvironmentService] { const services = new ServiceCollection(); const environmentService = new EnvironmentService(args, process.execPath); @@ -162,11 +161,12 @@ 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]; + return [new InstantiationService(services, true), instanceEnvironment, environmentService]; } - private initServices(environmentService: IEnvironmentService, configurationService: ConfigurationService, stateService: StateService): Promise { + private initServices(environmentService: INativeEnvironmentService, configurationService: ConfigurationService, stateService: StateService): Promise { // Environment service (paths) const environmentServiceInitialization = Promise.all([ @@ -187,7 +187,7 @@ class CodeMain { return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]); } - private patchEnvironment(environmentService: IEnvironmentService): IProcessEnvironment { + private patchEnvironment(environmentService: INativeEnvironmentService): IProcessEnvironment { const instanceEnvironment: IProcessEnvironment = { VSCODE_IPC_HOOK: environmentService.mainIPCHandle }; @@ -199,12 +199,12 @@ class CodeMain { } }); - assign(process.env, instanceEnvironment); + Object.assign(process.env, instanceEnvironment); return instanceEnvironment; } - private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { + private async doStartup(args: ParsedArgs, logService: ILogService, environmentService: INativeEnvironmentService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely @@ -260,7 +260,7 @@ class CodeMain { throw error; } - return this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, false); + return this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, false); } // Tests from CLI require to be the only instance currently @@ -276,7 +276,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 (!args.wait && !args.status) { startupWarningDialogHandle = setTimeout(() => { this.showStartupWarningDialog( localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), @@ -288,7 +288,7 @@ class CodeMain { const launchService = createChannelSender(client.getChannel('launch'), { disableMarshalling: true }); // Process Info - if (environmentService.args.status) { + if (args.status) { return instantiationService.invokeFunction(async accessor => { // Create a diagnostic service connected to the existing shared process const sharedProcessClient = await connect(environmentService.sharedIPCHandle, 'main'); @@ -310,7 +310,7 @@ class CodeMain { // Send environment over... logService.trace('Sending env to running instance...'); - await launchService.start(environmentService.args, process.env as IProcessEnvironment); + await launchService.start(args, process.env as IProcessEnvironment); // Cleanup client.dispose(); @@ -324,7 +324,7 @@ class CodeMain { } // Print --status usage info - if (environmentService.args.status) { + if (args.status) { logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); throw new ExpectedError('Terminating...'); @@ -342,7 +342,7 @@ class CodeMain { return server; } - private handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void { + private handleStartupDataDirError(environmentService: INativeEnvironmentService, error: NodeJS.ErrnoException): void { if (error.code === 'EACCES' || error.code === 'EPERM') { const directories = [environmentService.userDataPath]; diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 2ad547190cd..782cb228ea4 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assign } from 'vs/base/common/objects'; import { memoize } from 'vs/base/common/decorators'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { BrowserWindow, ipcMain, WebContents, Event as ElectronEvent } from 'electron'; @@ -14,6 +13,7 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; export class SharedProcess implements ISharedProcess { @@ -26,7 +26,7 @@ export class SharedProcess implements ISharedProcess { constructor( private readonly machineId: string, private userEnv: NodeJS.ProcessEnv, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, @IThemeMainService private readonly themeMainService: IThemeMainService @@ -47,13 +47,13 @@ export class SharedProcess implements ISharedProcess { disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer } }); - const config = assign({ + const config = { appRoot: this.environmentService.appRoot, machineId: this.machineId, nodeCachedDataDir: this.environmentService.nodeCachedDataDir, userEnv: this.userEnv, windowId: this.window.id - }); + }; const url = `${require.toUrl('vs/code/electron-browser/sharedProcess/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; this.window.loadURL(url); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 1f1c08a58f1..baf4a0ab02a 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -6,18 +6,20 @@ 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'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; 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 { parseArgs, OPTIONS, ParsedArgs } 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, 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,13 +27,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; @@ -58,6 +61,29 @@ const enum WindowError { CRASHED = 2 } +const enum ReadyState { + + /** + * This window has not loaded any HTML yet + */ + NONE, + + /** + * This window is loading HTML + */ + LOADING, + + /** + * This window is navigating to another HTML + */ + NAVIGATING, + + /** + * This window is done loading HTML + */ + READY +} + export class CodeWindow extends Disposable implements ICodeWindow { private static readonly MIN_WIDTH = 600; @@ -66,13 +92,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; @@ -80,27 +106,33 @@ 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; private readonly touchBarGroups: TouchBarSegmentedControl[]; + private currentHttpProxy?: string; + constructor( config: IWindowCreationOptions, @ILogService private readonly logService: ILogService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @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, @IBackupMainService private readonly backupMainService: IBackupMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IDialogMainService private readonly dialogMainService: IDialogMainService + @IDialogMainService private readonly dialogMainService: IDialogMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService ) { super(); @@ -223,14 +255,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; } @@ -244,6 +280,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { get isExtensionTestHost(): boolean { return !!(this.config && this.config.extensionTestsPath); } + get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.config?.debugId; } + setRepresentedFilename(filename: string): void { if (isMacintosh) { this.win.setRepresentedFilename(filename); @@ -260,6 +298,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; @@ -338,7 +392,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 }); } } @@ -387,6 +441,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.setFullScreen(false); this.setFullScreen(true); } + + this.sendWhenReady('vscode:displayChanged'); }, 100)); const displayChangedListener = () => simpleFullScreenScheduler.schedule(); @@ -441,12 +497,21 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => - this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as Record }))); + this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) }))); } private onWindowError(error: WindowError): void { this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive'); + // If we run extension tests from CLI, showing a dialog is not + // very helpful in this case. Rather, we bring down the test run + // to signal back a failing run. + if (this.isExtensionDevelopmentTestFromCli) { + this.lifecycleMainService.kill(1); + return; + } + + // Telemetry type WindowErrorClassification = { type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; }; @@ -531,6 +596,16 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.currentMenuBarVisibility = newMenuBarVisibility; this.setMenuBarVisibility(newMenuBarVisibility); } + // Do not set to empty configuration at startup if setting is empty to not override configuration through CLI options: + const newHttpProxy = (this.configurationService.getValue('http.proxy') || '').trim() || undefined; + if (newHttpProxy !== this.currentHttpProxy) { + this.currentHttpProxy = newHttpProxy; + this._win.webContents.session.setProxy({ + proxyRules: newHttpProxy || '', + proxyBypassRules: '', + pacScript: '', + }); + } } addTabbedWindow(window: ICodeWindow): void { @@ -539,7 +614,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 @@ -558,15 +633,15 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Add disable-extensions to the config, but do not preserve it on currentConfig or // pendingLoadConfig so that it is applied only on this load - const configuration = objects.assign({}, config); + const configuration = { ...config }; if (disableExtensions !== undefined) { configuration['disable-extensions'] = disableExtensions; } // 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); } } @@ -599,7 +674,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); @@ -626,7 +701,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; @@ -662,7 +737,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Config (combination of process.argv and window configuration) const environment = parseArgs(process.argv, OPTIONS); - const config = objects.assign(environment, windowConfiguration); + const config = Object.assign(environment, windowConfiguration); for (const key in config) { const configValue = (config as any)[key]; if (configValue === undefined || configValue === null || configValue === '' || configValue === false) { diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 609395b535a..81be5f203b3 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -6,10 +6,9 @@ import * as os from 'os'; import * as fs from 'fs'; import { spawn, ChildProcess, SpawnOptions } from 'child_process'; -import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv'; +import { buildHelpMessage, buildVersionMessage, OPTIONS, ParsedArgs } from 'vs/platform/environment/node/argv'; import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/common/product'; import * as paths from 'vs/base/common/path'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; @@ -128,7 +127,7 @@ export async function main(argv: string[]): Promise { delete env['ELECTRON_RUN_AS_NODE']; - const processCallbacks: ((child: ChildProcess) => Promise)[] = []; + const processCallbacks: ((child: ChildProcess) => Promise)[] = []; const verbose = args.verbose || args.status; if (verbose) { diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index f2e8279010b..e0c09d4f904 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -7,13 +7,13 @@ import { localize } from 'vs/nls'; import product from 'vs/platform/product/common/product'; import * as path from 'vs/base/common/path'; import * as semver from 'semver-umd'; - import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; +import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IExtensionManagementService, IExtensionGalleryService, IGalleryExtension, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -24,7 +24,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { mkdirp, writeFile } from 'vs/base/node/pfs'; import { getBaseLabel } from 'vs/base/common/labels'; @@ -49,7 +49,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); -const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-vscode.csharp'); +const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-dotnettools.csharp'); function getId(manifest: IExtensionManifest, withVersion?: boolean): string { if (withVersion) { @@ -74,7 +74,7 @@ export class Main { constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService ) { } @@ -328,21 +328,17 @@ export async function main(argv: ParsedArgs): Promise { const instantiationService: IInstantiationService = new InstantiationService(services); return instantiationService.invokeFunction(async accessor => { - const envService = accessor.get(IEnvironmentService); const stateService = accessor.get(IStateService); - const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService; + const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; const services = new ServiceCollection(); - - services.set(IRequestService, new SyncDescriptor(RequestService)); services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); const appenders: AppInsightsAppender[] = []; - if (isBuilt && !extensionDevelopmentLocationURI && !envService.args['disable-telemetry'] && product.enableTelemetry) { - + if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) { if (product.aiConfig && product.aiConfig.asimovKey) { appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, logService)); } diff --git a/src/vs/code/node/paths.ts b/src/vs/code/node/paths.ts index f605a4526f3..24c2529c587 100644 --- a/src/vs/code/node/paths.ts +++ b/src/vs/code/node/paths.ts @@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings'; import * as extpath from 'vs/base/common/extpath'; import * as platform from 'vs/base/common/platform'; import * as types from 'vs/base/common/types'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; export function validatePaths(args: ParsedArgs): ParsedArgs { @@ -19,8 +19,8 @@ export function validatePaths(args: ParsedArgs): ParsedArgs { args._ = []; } + // Normalize paths and watch out for goto line mode if (!args['remote']) { - // Normalize paths and watch out for goto line mode const paths = doValidatePaths(args._, args.goto); args._ = paths; } diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index b431b6d0514..174bb673a4a 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; import { isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; function getUnixShellEnvironment(logService: ILogService): Promise { const promise = new Promise((resolve, reject) => { @@ -21,10 +20,11 @@ function getUnixShellEnvironment(logService: ILogService): Promise; * This should only be done when Code itself is not launched * from within a shell. */ -export function getShellEnvironment(logService: ILogService, environmentService: IEnvironmentService): Promise { +export function getShellEnvironment(logService: ILogService, environmentService: INativeEnvironmentService): Promise { if (_shellEnv === undefined) { if (environmentService.args['disable-user-env-probe']) { logService.trace('getShellEnvironment: disable-user-env-probe set, skipping'); 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 ab975aa915d..b2a8e0c96e2 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -24,7 +24,8 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -855,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 { @@ -884,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 { @@ -934,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 { @@ -964,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 { @@ -1529,6 +1568,122 @@ export namespace CoreNavigationCommands { }); } +const columnSelectionCondition = ContextKeyExpr.and( + EditorContextKeys.textInputFocus, + EditorContextKeys.columnSelection +); +function registerColumnSelection(id: string, keybinding: number): void { + KeybindingsRegistry.registerKeybindingRule({ + id: id, + primary: keybinding, + when: columnSelectionCondition, + weight: CORE_WEIGHT + 1 + }); +} + +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectLeft.id, KeyMod.Shift | KeyCode.LeftArrow); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectRight.id, KeyMod.Shift | KeyCode.RightArrow); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectUp.id, KeyMod.Shift | KeyCode.UpArrow); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectPageUp.id, KeyMod.Shift | KeyCode.PageUp); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectDown.id, KeyMod.Shift | KeyCode.DownArrow); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectPageDown.id, KeyMod.Shift | KeyCode.PageDown); + +/** + * A command that will: + * 1. invoke a command on the focused editor. + * 2. otherwise, invoke a browser built-in command on the `activeElement`. + * 3. otherwise, invoke a command on the workbench active editor. + */ +abstract class EditorOrNativeTextInputCommand extends Command { + + public runCommand(accessor: ServicesAccessor, args: any): void { + + const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + // Only if editor text focus (i.e. not if editor has widget focus). + if (focusedEditor && focusedEditor.hasTextFocus()) { + return this.runEditorCommand(accessor, focusedEditor, args); + } + + // Ignore this action when user is focused on an element that allows for entering text + const activeElement = document.activeElement; + if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { + return this.runDOMCommand(); + } + + // Redirecting to active editor + const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor(); + if (activeEditor) { + activeEditor.focus(); + return this.runEditorCommand(accessor, activeEditor, args); + } + } + + public abstract runDOMCommand(): void; + public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void; +} + +class SelectAllCommand extends EditorOrNativeTextInputCommand { + constructor() { + super({ + id: 'editor.action.selectAll', + precondition: EditorContextKeys.textInputFocus, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: null, + primary: KeyMod.CtrlCmd | KeyCode.KEY_A + }, + menuOpts: [{ + menuId: MenuId.MenubarSelectionMenu, + group: '1_basic', + title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), + order: 1 + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('selectAll', "Select All"), + order: 1 + }] + }); + } + public runDOMCommand(): void { + document.execCommand('selectAll'); + } + public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + args = args || {}; + args.source = 'keyboard'; + CoreNavigationCommands.SelectAll.runEditorCommand(accessor, editor, args); + } +} + +class UndoCommand extends EditorOrNativeTextInputCommand { + public runDOMCommand(): void { + document.execCommand('undo'); + } + public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void { + if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) { + return; + } + editor.getModel().undo(); + } +} + +class RedoCommand extends EditorOrNativeTextInputCommand { + public runDOMCommand(): void { + document.execCommand('redo'); + } + public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void { + if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) { + return; + } + editor.getModel().redo(); + } +} + +function registerCommand(command: T): T { + command.register(); + return command; +} + export namespace CoreEditingCommands { export abstract class CoreEditingCommand extends EditorCommand { @@ -1614,7 +1769,7 @@ export namespace CoreEditingCommands { constructor() { super({ id: 'deleteLeft', - precondition: EditorContextKeys.writable, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1639,7 +1794,7 @@ export namespace CoreEditingCommands { constructor() { super({ id: 'deleteRight', - precondition: EditorContextKeys.writable, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1659,62 +1814,53 @@ export namespace CoreEditingCommands { } }); -} + export const Undo: UndoCommand = registerCommand(new UndoCommand({ + id: 'undo', + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyCode.KEY_Z + }, + menuOpts: [{ + menuId: MenuId.MenubarEditMenu, + group: '1_do', + title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), + order: 1 + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('undo', "Undo"), + order: 1 + }] + })); -function registerCommand(command: Command) { - command.register(); -} + export const DefaultUndo: UndoCommand = registerCommand(new UndoCommand({ id: 'default:undo', precondition: EditorContextKeys.writable })); -/** - * A command that will: - * 1. invoke a command on the focused editor. - * 2. otherwise, invoke a browser built-in command on the `activeElement`. - * 3. otherwise, invoke a command on the workbench active editor. - */ -class EditorOrNativeTextInputCommand extends Command { + export const Redo: RedoCommand = registerCommand(new RedoCommand({ + id: 'redo', + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z } + }, + menuOpts: [{ + menuId: MenuId.MenubarEditMenu, + group: '1_do', + title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), + order: 2 + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('redo', "Redo"), + order: 1 + }] + })); - private readonly _editorHandler: string | EditorCommand; - private readonly _inputHandler: string; - - constructor(opts: ICommandOptions & { editorHandler: string | EditorCommand; inputHandler: string; }) { - super(opts); - this._editorHandler = opts.editorHandler; - this._inputHandler = opts.inputHandler; - } - - public runCommand(accessor: ServicesAccessor, args: any): void { - - const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); - // Only if editor text focus (i.e. not if editor has widget focus). - if (focusedEditor && focusedEditor.hasTextFocus()) { - return this._runEditorHandler(accessor, focusedEditor, args); - } - - // Ignore this action when user is focused on an element that allows for entering text - const activeElement = document.activeElement; - if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { - document.execCommand(this._inputHandler); - return; - } - - // Redirecting to active editor - const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor(); - if (activeEditor) { - activeEditor.focus(); - return this._runEditorHandler(accessor, activeEditor, args); - } - } - - private _runEditorHandler(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - const HANDLER = this._editorHandler; - if (typeof HANDLER === 'string') { - editor.trigger('keyboard', HANDLER, args); - } else { - args = args || {}; - args.source = 'keyboard'; - HANDLER.runEditorCommand(accessor, editor, args); - } - } + export const DefaultRedo: RedoCommand = registerCommand(new RedoCommand({ id: 'default:redo', precondition: EditorContextKeys.writable })); } /** @@ -1743,78 +1889,7 @@ class EditorHandlerCommand extends Command { } } -registerCommand(new EditorOrNativeTextInputCommand({ - editorHandler: CoreNavigationCommands.SelectAll, - inputHandler: 'selectAll', - id: 'editor.action.selectAll', - precondition: EditorContextKeys.textInputFocus, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: null, - primary: KeyMod.CtrlCmd | KeyCode.KEY_A - }, - menuOpts: [{ - menuId: MenuId.MenubarSelectionMenu, - group: '1_basic', - title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), - order: 1 - }, { - menuId: MenuId.CommandPalette, - group: '', - title: nls.localize('selectAll', "Select All"), - order: 1 - }] -})); - -registerCommand(new EditorOrNativeTextInputCommand({ - editorHandler: Handler.Undo, - inputHandler: 'undo', - id: Handler.Undo, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_Z - }, - menuOpts: [{ - menuId: MenuId.MenubarEditMenu, - group: '1_do', - title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), - order: 1 - }, { - menuId: MenuId.CommandPalette, - group: '', - title: nls.localize('undo', "Undo"), - order: 1 - }] -})); -registerCommand(new EditorHandlerCommand('default:' + Handler.Undo, Handler.Undo)); - -registerCommand(new EditorOrNativeTextInputCommand({ - editorHandler: Handler.Redo, - inputHandler: 'redo', - id: Handler.Redo, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_Y, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z } - }, - menuOpts: [{ - menuId: MenuId.MenubarEditMenu, - group: '1_do', - title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), - order: 2 - }, { - menuId: MenuId.CommandPalette, - group: '', - title: nls.localize('redo', "Redo"), - order: 1 - }] -})); -registerCommand(new EditorHandlerCommand('default:' + Handler.Redo, Handler.Redo)); +registerCommand(new SelectAllCommand()); function registerOverwritableCommand(handlerId: string, description?: ICommandHandlerDescription): void { registerCommand(new EditorHandlerCommand('default:' + handlerId, handlerId)); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 0e5f6b0d454..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))) { @@ -352,6 +340,7 @@ class MouseDownOperation extends Disposable { if (!options.get(EditorOption.readOnly) && options.get(EditorOption.dragAndDrop) + && !options.get(EditorOption.columnSelection) && !this._mouseState.altKey // we don't support multiple mouse && e.detail < 2 // only single click on a selection can work && !this._isActive // the mouse is not down yet 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 85ee85e34f7..e8993db6133 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -13,7 +13,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer, IWordAtPosition } from 'vs/editor/common/model'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; @@ -333,6 +333,7 @@ export interface IOverviewRuler { */ export interface IEditorAriaOptions { activeDescendant: string | undefined; + role?: string; } /** @@ -567,6 +568,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ getRawOptions(): IEditorOptions; + /** + * @internal + */ + getConfiguredWordAtPosition(position: Position): IWordAtPosition | null; + /** * Get value of the current model attached to this editor. * @see `ITextModel.getValue` @@ -698,6 +704,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ getVisibleRanges(): Range[]; + /** + * @internal + */ + getVisibleRangesPlusViewportAboveBelow(): Range[]; + /** * Get the view zones. * @internal @@ -1008,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 f6d836aac53..1ddd9266efc 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -15,7 +15,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -39,26 +39,26 @@ export interface IDiffEditorContributionDescription { //#region Command export interface ICommandKeybindingsOptions extends IKeybindings { - kbExpr?: ContextKeyExpr | null; + kbExpr?: ContextKeyExpression | null; weight: number; } export interface ICommandMenuOptions { menuId: MenuId; group: string; order: number; - when?: ContextKeyExpr; + when?: ContextKeyExpression; title: string; } export interface ICommandOptions { id: string; - precondition: ContextKeyExpr | undefined; + precondition: ContextKeyExpression | undefined; kbOpts?: ICommandKeybindingsOptions; description?: ICommandHandlerDescription; menuOpts?: ICommandMenuOptions | ICommandMenuOptions[]; } export abstract class Command { public readonly id: string; - public readonly precondition: ContextKeyExpr | undefined; + public readonly precondition: ContextKeyExpression | undefined; private readonly _kbOpts: ICommandKeybindingsOptions | undefined; private readonly _menuOpts: ICommandMenuOptions | ICommandMenuOptions[] | undefined; private readonly _description: ICommandHandlerDescription | undefined; @@ -193,7 +193,7 @@ export abstract class EditorCommand extends Command { export interface IEditorActionContextMenuOptions { group: string; order: number; - when?: ContextKeyExpr; + when?: ContextKeyExpression; menuId?: MenuId; } export interface IActionOptions extends ICommandOptions { @@ -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/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 3d18020da6f..f6fdac9c874 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -16,6 +16,7 @@ export interface IBulkEditOptions { progress?: IProgress; showPreview?: boolean; label?: string; + quotableLabel?: string; } export interface IBulkEditResult { 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/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 600f303335e..81611253b01 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -149,6 +149,10 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe return result; } +const enum Constants { + SPAN_MODULO_LIMIT = 16384 +} + function renderLine(lineContent: string, initialVisibleColumn: number, tabSize: number, width: number, sb: IStringBuilder): [number[], number[]] { sb.appendASCIIString('
MinimapCharRenderer; - /** * container dom node left position (in CSS px) */ @@ -107,6 +91,13 @@ class MinimapOptions { */ public readonly canvasOuterHeight: number; + public readonly isSampling: boolean; + public readonly editorHeight: number; + public readonly fontScale: number; + public readonly minimapLineHeight: number; + public readonly minimapCharWidth: number; + + public readonly charRenderer: () => MinimapCharRenderer; public readonly backgroundColor: RGBA8; constructor(configuration: IConfiguration, theme: EditorTheme, tokensColorTracker: MinimapTokensColorTracker) { @@ -114,13 +105,13 @@ class MinimapOptions { const pixelRatio = options.get(EditorOption.pixelRatio); const layoutInfo = options.get(EditorOption.layoutInfo); const fontInfo = options.get(EditorOption.fontInfo); + const minimapOpts = options.get(EditorOption.minimap); this.renderMinimap = layoutInfo.renderMinimap | 0; + this.size = minimapOpts.size; + this.minimapHeightIsEditorHeight = layoutInfo.minimapHeightIsEditorHeight; this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); - const minimapOpts = options.get(EditorOption.minimap); this.showSlider = minimapOpts.showSlider; - this.fontScale = Math.round(minimapOpts.scale * pixelRatio); - this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this.lineHeight = options.get(EditorOption.lineHeight); @@ -128,12 +119,18 @@ class MinimapOptions { this.minimapWidth = layoutInfo.minimapWidth; this.minimapHeight = layoutInfo.height; - this.canvasInnerWidth = Math.floor(pixelRatio * this.minimapWidth); - this.canvasInnerHeight = Math.floor(pixelRatio * this.minimapHeight); + this.canvasInnerWidth = layoutInfo.minimapCanvasInnerWidth; + this.canvasInnerHeight = layoutInfo.minimapCanvasInnerHeight; + this.canvasOuterWidth = layoutInfo.minimapCanvasOuterWidth; + this.canvasOuterHeight = layoutInfo.minimapCanvasOuterHeight; - this.canvasOuterWidth = this.canvasInnerWidth / pixelRatio; - this.canvasOuterHeight = this.canvasInnerHeight / pixelRatio; + this.isSampling = layoutInfo.minimapIsSampling; + this.editorHeight = layoutInfo.height; + this.fontScale = layoutInfo.minimapScale; + this.minimapLineHeight = layoutInfo.minimapLineHeight; + this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale; + this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily)); this.backgroundColor = MinimapOptions._getMinimapBackground(theme, tokensColorTracker); } @@ -147,12 +144,13 @@ class MinimapOptions { public equals(other: MinimapOptions): boolean { return (this.renderMinimap === other.renderMinimap + && this.size === other.size + && this.minimapHeightIsEditorHeight === other.minimapHeightIsEditorHeight && this.scrollBeyondLastLine === other.scrollBeyondLastLine && this.showSlider === other.showSlider && this.pixelRatio === other.pixelRatio && this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth && this.lineHeight === other.lineHeight - && this.fontScale === other.fontScale && this.minimapLeft === other.minimapLeft && this.minimapWidth === other.minimapWidth && this.minimapHeight === other.minimapHeight @@ -160,7 +158,12 @@ class MinimapOptions { && this.canvasInnerHeight === other.canvasInnerHeight && this.canvasOuterWidth === other.canvasOuterWidth && this.canvasOuterHeight === other.canvasOuterHeight - && this.backgroundColor.equals(other.backgroundColor) + && this.isSampling === other.isSampling + && this.editorHeight === other.editorHeight + && this.fontScale === other.fontScale + && this.minimapLineHeight === other.minimapLineHeight + && this.minimapCharWidth === other.minimapCharWidth + && this.backgroundColor && this.backgroundColor.equals(other.backgroundColor) ); } } @@ -177,6 +180,7 @@ class MinimapLayout { */ public readonly scrollHeight: number; + public readonly sliderNeeded: boolean; private readonly _computedSliderRatio: number; /** @@ -200,6 +204,7 @@ class MinimapLayout { constructor( scrollTop: number, scrollHeight: number, + sliderNeeded: boolean, computedSliderRatio: number, sliderTop: number, sliderHeight: number, @@ -208,6 +213,7 @@ class MinimapLayout { ) { this.scrollTop = scrollTop; this.scrollHeight = scrollHeight; + this.sliderNeeded = sliderNeeded; this._computedSliderRatio = computedSliderRatio; this.sliderTop = sliderTop; this.sliderHeight = sliderHeight; @@ -234,15 +240,31 @@ class MinimapLayout { viewportHeight: number, viewportContainsWhitespaceGaps: boolean, lineCount: number, + realLineCount: number, scrollTop: number, scrollHeight: number, previousLayout: MinimapLayout | null ): MinimapLayout { const pixelRatio = options.pixelRatio; - const minimapLineHeight = getMinimapLineHeight(options.renderMinimap, options.fontScale); + const minimapLineHeight = options.minimapLineHeight; const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight); const lineHeight = options.lineHeight; + if (options.minimapHeightIsEditorHeight) { + const logicalScrollHeight = ( + realLineCount * options.lineHeight + + (options.scrollBeyondLastLine ? viewportHeight - options.lineHeight : 0) + ); + const sliderHeight = Math.max(1, Math.floor(viewportHeight * viewportHeight / logicalScrollHeight)); + const maxMinimapSliderTop = Math.max(0, options.minimapHeight - sliderHeight); + // The slider can move from 0 to `maxMinimapSliderTop` + // in the same way `scrollTop` can move from 0 to `scrollHeight` - `viewportHeight`. + const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight); + const sliderTop = (scrollTop * computedSliderRatio); + const sliderNeeded = (maxMinimapSliderTop > 0); + return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, 1, lineCount); + } + // The visible line count in a viewport can change due to a number of reasons: // a) with the same viewport width, different scroll positions can result in partial lines being visible: // e.g. for a line height of 20, and a viewport height of 600 @@ -283,14 +305,14 @@ class MinimapLayout { let extraLinesAtTheBottom = 0; if (options.scrollBeyondLastLine) { const expectedViewportLineCount = viewportHeight / lineHeight; - extraLinesAtTheBottom = expectedViewportLineCount; + extraLinesAtTheBottom = expectedViewportLineCount - 1; } if (minimapLinesFitting >= lineCount + extraLinesAtTheBottom) { // All lines fit in the minimap const startLineNumber = 1; const endLineNumber = lineCount; - - return new MinimapLayout(scrollTop, scrollHeight, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); + const sliderNeeded = (maxMinimapSliderTop > 0); + return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); } else { let startLineNumber = Math.max(1, Math.floor(viewportStartLineNumber - sliderTop * pixelRatio / minimapLineHeight)); @@ -309,7 +331,7 @@ class MinimapLayout { const endLineNumber = Math.min(lineCount, startLineNumber + minimapLinesFitting - 1); - return new MinimapLayout(scrollTop, scrollHeight, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); + return new MinimapLayout(scrollTop, scrollHeight, true, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber); } } } @@ -391,17 +413,17 @@ class RenderData { }; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { - return this._renderedLines.onLinesChanged(e.fromLineNumber, e.toLineNumber); + public onLinesChanged(changeFromLineNumber: number, changeToLineNumber: number): boolean { + return this._renderedLines.onLinesChanged(changeFromLineNumber, changeToLineNumber); } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): void { - this._renderedLines.onLinesDeleted(e.fromLineNumber, e.toLineNumber); + public onLinesDeleted(deleteFromLineNumber: number, deleteToLineNumber: number): void { + this._renderedLines.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber); } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): void { - this._renderedLines.onLinesInserted(e.fromLineNumber, e.toLineNumber); + public onLinesInserted(insertFromLineNumber: number, insertToLineNumber: number): void { + this._renderedLines.onLinesInserted(insertFromLineNumber, insertToLineNumber); } - public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { - return this._renderedLines.onTokensChanged(e.ranges); + public onTokensChanged(ranges: { fromLineNumber: number; toLineNumber: number; }[]): boolean { + return this._renderedLines.onTokensChanged(ranges); } } @@ -458,9 +480,559 @@ class MinimapBuffers { } } -export class Minimap extends ViewPart { +export interface IMinimapModel { + readonly tokensColorTracker: MinimapTokensColorTracker; + readonly options: MinimapOptions; + + getLineCount(): number; + getRealLineCount(): number; + getLineContent(lineNumber: number): string; + getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[]; + getSelections(): Selection[]; + getMinimapDecorationsInViewport(startLineNumber: number, endLineNumber: number): ViewModelDecoration[]; + getOptions(): TextModelResolvedOptions; + revealLineNumber(lineNumber: number): void; + setScrollTop(scrollTop: number): void; +} + +interface IMinimapRenderingContext { + readonly viewportContainsWhitespaceGaps: boolean; + + readonly scrollWidth: number; + readonly scrollHeight: number; + + readonly viewportStartLineNumber: number; + readonly viewportEndLineNumber: number; + + readonly scrollTop: number; + readonly scrollLeft: number; + + readonly viewportWidth: number; + readonly viewportHeight: number; +} + +interface SamplingStateLinesDeletedEvent { + type: 'deleted'; + _oldIndex: number; + deleteFromLineNumber: number; + deleteToLineNumber: number; +} + +interface SamplingStateLinesInsertedEvent { + type: 'inserted'; + _i: number; + insertFromLineNumber: number; + insertToLineNumber: number; +} + +interface SamplingStateFlushEvent { + type: 'flush'; +} + +type SamplingStateEvent = SamplingStateLinesInsertedEvent | SamplingStateLinesDeletedEvent | SamplingStateFlushEvent; + +class MinimapSamplingState { + + public static compute(options: MinimapOptions, viewLineCount: number, oldSamplingState: MinimapSamplingState | null): [MinimapSamplingState | null, SamplingStateEvent[]] { + if (options.renderMinimap === RenderMinimap.None || !options.isSampling) { + return [null, []]; + } + + // ratio is intentionally not part of the layout to avoid the layout changing all the time + // so we need to recompute it again... + const pixelRatio = options.pixelRatio; + const lineHeight = options.lineHeight; + const scrollBeyondLastLine = options.scrollBeyondLastLine; + const { minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({ + viewLineCount: viewLineCount, + scrollBeyondLastLine: scrollBeyondLastLine, + height: options.editorHeight, + lineHeight: lineHeight, + pixelRatio: pixelRatio + }); + const ratio = viewLineCount / minimapLineCount; + const halfRatio = ratio / 2; + + if (!oldSamplingState || oldSamplingState.minimapLines.length === 0) { + let result: number[] = []; + result[0] = 1; + if (minimapLineCount > 1) { + for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) { + result[i] = Math.round(i * ratio + halfRatio); + } + result[minimapLineCount - 1] = viewLineCount; + } + return [new MinimapSamplingState(ratio, result), []]; + } + + const oldMinimapLines = oldSamplingState.minimapLines; + const oldLength = oldMinimapLines.length; + let result: number[] = []; + let oldIndex = 0; + let oldDeltaLineCount = 0; + let minViewLineNumber = 1; + const MAX_EVENT_COUNT = 10; // generate at most 10 events, if there are more than 10 changes, just flush all previous data + let events: SamplingStateEvent[] = []; + let lastEvent: SamplingStateEvent | null = null; + for (let i = 0; i < minimapLineCount; i++) { + const fromViewLineNumber = Math.max(minViewLineNumber, Math.round(i * ratio)); + const toViewLineNumber = Math.max(fromViewLineNumber, Math.round((i + 1) * ratio)); + + while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromViewLineNumber) { + if (events.length < MAX_EVENT_COUNT) { + const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount; + if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) { + lastEvent.deleteToLineNumber++; + } else { + lastEvent = { type: 'deleted', _oldIndex: oldIndex, deleteFromLineNumber: oldMinimapLineNumber, deleteToLineNumber: oldMinimapLineNumber }; + events.push(lastEvent); + } + oldDeltaLineCount--; + } + oldIndex++; + } + + let selectedViewLineNumber: number; + if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toViewLineNumber) { + // reuse the old sampled line + selectedViewLineNumber = oldMinimapLines[oldIndex]; + oldIndex++; + } else { + if (i === 0) { + selectedViewLineNumber = 1; + } else if (i + 1 === minimapLineCount) { + selectedViewLineNumber = viewLineCount; + } else { + selectedViewLineNumber = Math.round(i * ratio + halfRatio); + } + if (events.length < MAX_EVENT_COUNT) { + const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount; + if (lastEvent && lastEvent.type === 'inserted' && lastEvent._i === i - 1) { + lastEvent.insertToLineNumber++; + } else { + lastEvent = { type: 'inserted', _i: i, insertFromLineNumber: oldMinimapLineNumber, insertToLineNumber: oldMinimapLineNumber }; + events.push(lastEvent); + } + oldDeltaLineCount++; + } + } + + result[i] = selectedViewLineNumber; + minViewLineNumber = selectedViewLineNumber; + } + + if (events.length < MAX_EVENT_COUNT) { + while (oldIndex < oldLength) { + const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount; + if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) { + lastEvent.deleteToLineNumber++; + } else { + lastEvent = { type: 'deleted', _oldIndex: oldIndex, deleteFromLineNumber: oldMinimapLineNumber, deleteToLineNumber: oldMinimapLineNumber }; + events.push(lastEvent); + } + oldDeltaLineCount--; + oldIndex++; + } + } else { + // too many events, just give up + events = [{ type: 'flush' }]; + } + + return [new MinimapSamplingState(ratio, result), events]; + } + + constructor( + public readonly samplingRatio: number, + public readonly minimapLines: number[] + ) { + } + + public modelLineToMinimapLine(lineNumber: number): number { + return Math.min(this.minimapLines.length, Math.max(1, Math.round(lineNumber / this.samplingRatio))); + } + + /** + * Will return null if the model line ranges are not intersecting with a sampled model line. + */ + public modelLineRangeToMinimapLineRange(fromLineNumber: number, toLineNumber: number): [number, number] | null { + let fromLineIndex = this.modelLineToMinimapLine(fromLineNumber) - 1; + while (fromLineIndex > 0 && this.minimapLines[fromLineIndex - 1] >= fromLineNumber) { + fromLineIndex--; + } + let toLineIndex = this.modelLineToMinimapLine(toLineNumber) - 1; + while (toLineIndex + 1 < this.minimapLines.length && this.minimapLines[toLineIndex + 1] <= toLineNumber) { + toLineIndex++; + } + if (fromLineIndex === toLineIndex) { + const sampledLineNumber = this.minimapLines[fromLineIndex]; + if (sampledLineNumber < fromLineNumber || sampledLineNumber > toLineNumber) { + // This line is not part of the sampled lines ==> nothing to do + return null; + } + } + return [fromLineIndex + 1, toLineIndex + 1]; + } + + /** + * Will always return a range, even if it is not intersecting with a sampled model line. + */ + public decorationLineRangeToMinimapLineRange(startLineNumber: number, endLineNumber: number): [number, number] { + let minimapLineStart = this.modelLineToMinimapLine(startLineNumber); + let minimapLineEnd = this.modelLineToMinimapLine(endLineNumber); + if (startLineNumber !== endLineNumber && minimapLineEnd === minimapLineStart) { + if (minimapLineEnd === this.minimapLines.length) { + if (minimapLineStart > 1) { + minimapLineStart--; + } + } else { + minimapLineEnd++; + } + } + return [minimapLineStart, minimapLineEnd]; + } + + public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): [number, number] { + // have the mapping be sticky + const deletedLineCount = e.toLineNumber - e.fromLineNumber + 1; + let changeStartIndex = this.minimapLines.length; + let changeEndIndex = 0; + for (let i = this.minimapLines.length - 1; i >= 0; i--) { + if (this.minimapLines[i] < e.fromLineNumber) { + break; + } + if (this.minimapLines[i] <= e.toLineNumber) { + // this line got deleted => move to previous available + this.minimapLines[i] = Math.max(1, e.fromLineNumber - 1); + changeStartIndex = Math.min(changeStartIndex, i); + changeEndIndex = Math.max(changeEndIndex, i); + } else { + this.minimapLines[i] -= deletedLineCount; + } + } + return [changeStartIndex, changeEndIndex]; + } + + public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): void { + // have the mapping be sticky + const insertedLineCount = e.toLineNumber - e.fromLineNumber + 1; + for (let i = this.minimapLines.length - 1; i >= 0; i--) { + if (this.minimapLines[i] < e.fromLineNumber) { + break; + } + this.minimapLines[i] += insertedLineCount; + } + } +} + +export class Minimap extends ViewPart implements IMinimapModel { + + public readonly tokensColorTracker: MinimapTokensColorTracker; + + private _selections: Selection[]; + private _minimapSelections: Selection[] | null; + + public options: MinimapOptions; + + private _samplingState: MinimapSamplingState | null; + private _shouldCheckSampling: boolean; + + private _actual: InnerMinimap; + + constructor(context: ViewContext) { + super(context); + + this.tokensColorTracker = MinimapTokensColorTracker.getInstance(); + + this._selections = []; + this._minimapSelections = null; + + this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker); + const [samplingState,] = MinimapSamplingState.compute(this.options, this._context.model.getLineCount(), null); + this._samplingState = samplingState; + this._shouldCheckSampling = false; + + this._actual = new InnerMinimap(context.theme, this); + } + + public dispose(): void { + this._actual.dispose(); + super.dispose(); + } + + public getDomNode(): FastDomNode { + return this._actual.getDomNode(); + } + + private _onOptionsMaybeChanged(): boolean { + const opts = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker); + if (this.options.equals(opts)) { + return false; + } + this.options = opts; + this._recreateLineSampling(); + this._actual.onDidChangeOptions(); + return true; + } + + // ---- begin view event handlers + + public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + return this._onOptionsMaybeChanged(); + } + public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + this._selections = e.selections; + this._minimapSelections = null; + return this._actual.onSelectionChanged(); + } + public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + if (e.affectsMinimap) { + return this._actual.onDecorationsChanged(); + } + return false; + } + public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + if (this._samplingState) { + this._shouldCheckSampling = true; + } + return this._actual.onFlushed(); + } + public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + if (this._samplingState) { + const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(e.fromLineNumber, e.toLineNumber); + if (minimapLineRange) { + return this._actual.onLinesChanged(minimapLineRange[0], minimapLineRange[1]); + } else { + return false; + } + } else { + return this._actual.onLinesChanged(e.fromLineNumber, e.toLineNumber); + } + } + public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + if (this._samplingState) { + const [changeStartIndex, changeEndIndex] = this._samplingState.onLinesDeleted(e); + if (changeStartIndex <= changeEndIndex) { + this._actual.onLinesChanged(changeStartIndex + 1, changeEndIndex + 1); + } + this._shouldCheckSampling = true; + return true; + } else { + return this._actual.onLinesDeleted(e.fromLineNumber, e.toLineNumber); + } + } + public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + if (this._samplingState) { + this._samplingState.onLinesInserted(e); + this._shouldCheckSampling = true; + return true; + } else { + return this._actual.onLinesInserted(e.fromLineNumber, e.toLineNumber); + } + } + public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + return this._actual.onScrollChanged(); + } + public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + this._context.model.invalidateMinimapColorCache(); + this._actual.onThemeChanged(); + this._onOptionsMaybeChanged(); + return true; + } + public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { + if (this._samplingState) { + let ranges: { fromLineNumber: number; toLineNumber: number; }[] = []; + for (const range of e.ranges) { + const minimapLineRange = this._samplingState.modelLineRangeToMinimapLineRange(range.fromLineNumber, range.toLineNumber); + if (minimapLineRange) { + ranges.push({ fromLineNumber: minimapLineRange[0], toLineNumber: minimapLineRange[1] }); + } + } + if (ranges.length) { + return this._actual.onTokensChanged(ranges); + } else { + return false; + } + } else { + return this._actual.onTokensChanged(e.ranges); + } + } + public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean { + return this._actual.onTokensColorsChanged(); + } + public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + return this._actual.onZonesChanged(); + } + + // --- end event handlers + + public prepareRender(ctx: RenderingContext): void { + if (this._shouldCheckSampling) { + this._shouldCheckSampling = false; + this._recreateLineSampling(); + } + } + + public render(ctx: RestrictedRenderingContext): void { + let viewportStartLineNumber = ctx.visibleRange.startLineNumber; + let viewportEndLineNumber = ctx.visibleRange.endLineNumber; + + if (this._samplingState) { + viewportStartLineNumber = this._samplingState.modelLineToMinimapLine(viewportStartLineNumber); + viewportEndLineNumber = this._samplingState.modelLineToMinimapLine(viewportEndLineNumber); + } + + const minimapCtx: IMinimapRenderingContext = { + viewportContainsWhitespaceGaps: (ctx.viewportData.whitespaceViewportData.length > 0), + + scrollWidth: ctx.scrollWidth, + scrollHeight: ctx.scrollHeight, + + viewportStartLineNumber: viewportStartLineNumber, + viewportEndLineNumber: viewportEndLineNumber, + + scrollTop: ctx.scrollTop, + scrollLeft: ctx.scrollLeft, + + viewportWidth: ctx.viewportWidth, + viewportHeight: ctx.viewportHeight, + }; + this._actual.render(minimapCtx); + } + + //#region IMinimapModel + + private _recreateLineSampling(): void { + this._minimapSelections = null; + + const wasSampling = Boolean(this._samplingState); + const [samplingState, events] = MinimapSamplingState.compute(this.options, this._context.model.getLineCount(), this._samplingState); + this._samplingState = samplingState; + + if (wasSampling && this._samplingState) { + // was sampling, is sampling + for (const event of events) { + switch (event.type) { + case 'deleted': + this._actual.onLinesDeleted(event.deleteFromLineNumber, event.deleteToLineNumber); + break; + case 'inserted': + this._actual.onLinesInserted(event.insertFromLineNumber, event.insertToLineNumber); + break; + case 'flush': + this._actual.onFlushed(); + break; + } + } + } + } + + public getLineCount(): number { + if (this._samplingState) { + return this._samplingState.minimapLines.length; + } + return this._context.model.getLineCount(); + } + + public getRealLineCount(): number { + return this._context.model.getLineCount(); + } + + public getLineContent(lineNumber: number): string { + if (this._samplingState) { + return this._context.model.getLineContent(this._samplingState.minimapLines[lineNumber - 1]); + } + return this._context.model.getLineContent(lineNumber); + } + + public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): (ViewLineData | null)[] { + if (this._samplingState) { + let result: (ViewLineData | null)[] = []; + for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) { + if (needed[lineIndex]) { + result[lineIndex] = this._context.model.getViewLineData(this._samplingState.minimapLines[startLineNumber + lineIndex - 1]); + } else { + result[lineIndex] = null; + } + } + return result; + } + return this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed).data; + } + + public getSelections(): Selection[] { + if (this._minimapSelections === null) { + if (this._samplingState) { + this._minimapSelections = []; + for (const selection of this._selections) { + const [minimapLineStart, minimapLineEnd] = this._samplingState.decorationLineRangeToMinimapLineRange(selection.startLineNumber, selection.endLineNumber); + this._minimapSelections.push(new Selection(minimapLineStart, selection.startColumn, minimapLineEnd, selection.endColumn)); + } + } else { + this._minimapSelections = this._selections; + } + } + return this._minimapSelections; + } + + public getMinimapDecorationsInViewport(startLineNumber: number, endLineNumber: number): ViewModelDecoration[] { + let visibleRange: Range; + if (this._samplingState) { + const modelStartLineNumber = this._samplingState.minimapLines[startLineNumber - 1]; + const modelEndLineNumber = this._samplingState.minimapLines[endLineNumber - 1]; + visibleRange = new Range(modelStartLineNumber, 1, modelEndLineNumber, this._context.model.getLineMaxColumn(modelEndLineNumber)); + } else { + visibleRange = new Range(startLineNumber, 1, endLineNumber, this._context.model.getLineMaxColumn(endLineNumber)); + } + const decorations = this._context.model.getDecorationsInViewport(visibleRange); + + if (this._samplingState) { + let result: ViewModelDecoration[] = []; + for (const decoration of decorations) { + if (!decoration.options.minimap) { + continue; + } + const range = decoration.range; + const minimapStartLineNumber = this._samplingState.modelLineToMinimapLine(range.startLineNumber); + const minimapEndLineNumber = this._samplingState.modelLineToMinimapLine(range.endLineNumber); + result.push(new ViewModelDecoration(new Range(minimapStartLineNumber, range.startColumn, minimapEndLineNumber, range.endColumn), decoration.options)); + } + return result; + } + return decorations; + } + + public getOptions(): TextModelResolvedOptions { + return this._context.model.getOptions(); + } + + public revealLineNumber(lineNumber: number): void { + if (this._samplingState) { + lineNumber = this._samplingState.minimapLines[lineNumber - 1]; + } + this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( + 'mouse', + new Range(lineNumber, 1, lineNumber, 1), + null, + viewEvents.VerticalRevealType.Center, + false, + ScrollType.Smooth + )); + } + + public setScrollTop(scrollTop: number): void { + this._context.viewLayout.setScrollPositionNow({ + scrollTop: scrollTop + }); + } + + //#endregion +} + +class InnerMinimap extends Disposable { + + private readonly _theme: EditorTheme; + private readonly _model: IMinimapModel; - private readonly _tokensColorTracker: MinimapTokensColorTracker; private readonly _domNode: FastDomNode; private readonly _shadow: FastDomNode; private readonly _canvas: FastDomNode; @@ -475,22 +1047,24 @@ export class Minimap extends ViewPart { private readonly _sliderTouchMoveListener: IDisposable; private readonly _sliderTouchEndListener: IDisposable; - private _options: MinimapOptions; private _lastRenderData: RenderData | null; - private _selections: Selection[] = []; private _selectionColor: Color | undefined; private _renderDecorations: boolean = false; private _gestureInProgress: boolean = false; private _buffers: MinimapBuffers | null; - constructor(context: ViewContext) { - super(context); + constructor( + theme: EditorTheme, + model: IMinimapModel + ) { + super(); + + this._theme = theme; + this._model = model; - this._tokensColorTracker = MinimapTokensColorTracker.getInstance(); - this._options = new MinimapOptions(this._context.configuration, this._context.theme, this._tokensColorTracker); this._lastRenderData = null; this._buffers = null; - this._selectionColor = this._context.theme.getColor(minimapSelection); + this._selectionColor = this._theme.getColor(minimapSelection); this._domNode = createFastDomNode(document.createElement('div')); PartFingerprints.write(this._domNode, PartFingerprint.Minimap); @@ -531,27 +1105,30 @@ export class Minimap extends ViewPart { this._mouseDownListener = dom.addStandardDisposableListener(this._domNode.domNode, 'mousedown', (e) => { e.preventDefault(); - const renderMinimap = this._options.renderMinimap; + const renderMinimap = this._model.options.renderMinimap; if (renderMinimap === RenderMinimap.None) { return; } if (!this._lastRenderData) { return; } - const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); - const internalOffsetY = this._options.pixelRatio * e.browserEvent.offsetY; + if (this._model.options.size !== 'proportional') { + if (e.leftButton && this._lastRenderData) { + // pretend the click occured in the center of the slider + const position = dom.getDomNodePagePosition(this._slider.domNode); + const initialPosY = position.top + position.height / 2; + this._startSliderDragging(e.buttons, e.posx, initialPosY, e.posy, this._lastRenderData.renderedLayout); + } + return; + } + const minimapLineHeight = this._model.options.minimapLineHeight; + const internalOffsetY = (this._model.options.canvasInnerHeight / this._model.options.canvasOuterHeight) * e.browserEvent.offsetY; const lineIndex = Math.floor(internalOffsetY / minimapLineHeight); let lineNumber = lineIndex + this._lastRenderData.renderedLayout.startLineNumber; - lineNumber = Math.min(lineNumber, this._context.model.getLineCount()); + lineNumber = Math.min(lineNumber, this._model.getLineCount()); - this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( - 'mouse', - new Range(lineNumber, 1, lineNumber, 1), - viewEvents.VerticalRevealType.Center, - false, - ScrollType.Smooth - )); + this._model.revealLineNumber(lineNumber); }); this._sliderMouseMoveMonitor = new GlobalMouseMoveMonitor(); @@ -560,36 +1137,7 @@ export class Minimap extends ViewPart { e.preventDefault(); e.stopPropagation(); if (e.leftButton && this._lastRenderData) { - - const initialMousePosition = e.posy; - const initialMouseOrthogonalPosition = e.posx; - const initialSliderState = this._lastRenderData.renderedLayout; - this._slider.toggleClassName('active', true); - - this._sliderMouseMoveMonitor.startMonitoring( - e.target, - e.buttons, - standardMouseMoveMerger, - (mouseMoveData: IStandardMouseMoveEventData) => { - const mouseOrthogonalDelta = Math.abs(mouseMoveData.posx - initialMouseOrthogonalPosition); - - if (platform.isWindows && mouseOrthogonalDelta > MOUSE_DRAG_RESET_DISTANCE) { - // The mouse has wondered away from the scrollbar => reset dragging - this._context.viewLayout.setScrollPositionNow({ - scrollTop: initialSliderState.scrollTop - }); - return; - } - - const mouseDelta = mouseMoveData.posy - initialMousePosition; - this._context.viewLayout.setScrollPositionNow({ - scrollTop: initialSliderState.getDesiredScrollTopFromDelta(mouseDelta) - }); - }, - () => { - this._slider.toggleClassName('active', false); - } - ); + this._startSliderDragging(e.buttons, e.posx, e.posy, e.posy, this._lastRenderData.renderedLayout); } }); @@ -602,15 +1150,15 @@ export class Minimap extends ViewPart { this._gestureInProgress = true; this.scrollDueToTouchEvent(e); } - }); + }, { passive: false }); - this._sliderTouchMoveListener = dom.addStandardDisposableListener(this._domNode.domNode, EventType.Change, (e: GestureEvent) => { + this._sliderTouchMoveListener = dom.addDisposableListener(this._domNode.domNode, EventType.Change, (e: GestureEvent) => { e.preventDefault(); e.stopPropagation(); if (this._lastRenderData && this._gestureInProgress) { this.scrollDueToTouchEvent(e); } - }); + }, { passive: false }); this._sliderTouchEndListener = dom.addStandardDisposableListener(this._domNode.domNode, EventType.End, (e: GestureEvent) => { e.preventDefault(); @@ -620,12 +1168,41 @@ export class Minimap extends ViewPart { }); } + private _startSliderDragging(initialButtons: number, initialPosX: number, initialPosY: number, posy: number, initialSliderState: MinimapLayout): void { + this._slider.toggleClassName('active', true); + + const handleMouseMove = (posy: number, posx: number) => { + const mouseOrthogonalDelta = Math.abs(posx - initialPosX); + + if (platform.isWindows && mouseOrthogonalDelta > MOUSE_DRAG_RESET_DISTANCE) { + // The mouse has wondered away from the scrollbar => reset dragging + this._model.setScrollTop(initialSliderState.scrollTop); + return; + } + + const mouseDelta = posy - initialPosY; + this._model.setScrollTop(initialSliderState.getDesiredScrollTopFromDelta(mouseDelta)); + }; + + if (posy !== initialPosY) { + handleMouseMove(posy, initialPosX); + } + + this._sliderMouseMoveMonitor.startMonitoring( + this._slider.domNode, + initialButtons, + standardMouseMoveMerger, + (mouseMoveData: IStandardMouseMoveEventData) => handleMouseMove(mouseMoveData.posy, mouseMoveData.posx), + () => { + this._slider.toggleClassName('active', false); + } + ); + } + private scrollDueToTouchEvent(touch: GestureEvent) { const startY = this._domNode.domNode.getBoundingClientRect().top; const scrollTop = this._lastRenderData!.renderedLayout.getDesiredScrollTopFromTouchLocation(touch.pageY - startY); - this._context.viewLayout.setScrollPositionNow({ - scrollTop: scrollTop - }); + this._model.setScrollTop(scrollTop); } public dispose(): void { @@ -640,7 +1217,7 @@ export class Minimap extends ViewPart { } private _getMinimapDomNodeClassName(): string { - if (this._options.showSlider === 'always') { + if (this._model.options.showSlider === 'always') { return 'minimap slider-always'; } return 'minimap slider-mouseover'; @@ -651,124 +1228,105 @@ export class Minimap extends ViewPart { } private _applyLayout(): void { - this._domNode.setLeft(this._options.minimapLeft); - this._domNode.setWidth(this._options.minimapWidth); - this._domNode.setHeight(this._options.minimapHeight); - this._shadow.setHeight(this._options.minimapHeight); + this._domNode.setLeft(this._model.options.minimapLeft); + this._domNode.setWidth(this._model.options.minimapWidth); + this._domNode.setHeight(this._model.options.minimapHeight); + this._shadow.setHeight(this._model.options.minimapHeight); - this._canvas.setWidth(this._options.canvasOuterWidth); - this._canvas.setHeight(this._options.canvasOuterHeight); - this._canvas.domNode.width = this._options.canvasInnerWidth; - this._canvas.domNode.height = this._options.canvasInnerHeight; + this._canvas.setWidth(this._model.options.canvasOuterWidth); + this._canvas.setHeight(this._model.options.canvasOuterHeight); + this._canvas.domNode.width = this._model.options.canvasInnerWidth; + this._canvas.domNode.height = this._model.options.canvasInnerHeight; - this._decorationsCanvas.setWidth(this._options.canvasOuterWidth); - this._decorationsCanvas.setHeight(this._options.canvasOuterHeight); - this._decorationsCanvas.domNode.width = this._options.canvasInnerWidth; - this._decorationsCanvas.domNode.height = this._options.canvasInnerHeight; + this._decorationsCanvas.setWidth(this._model.options.canvasOuterWidth); + this._decorationsCanvas.setHeight(this._model.options.canvasOuterHeight); + this._decorationsCanvas.domNode.width = this._model.options.canvasInnerWidth; + this._decorationsCanvas.domNode.height = this._model.options.canvasInnerHeight; - this._slider.setWidth(this._options.minimapWidth); + this._slider.setWidth(this._model.options.minimapWidth); } private _getBuffer(): ImageData | null { if (!this._buffers) { - if (this._options.canvasInnerWidth > 0 && this._options.canvasInnerHeight > 0) { + if (this._model.options.canvasInnerWidth > 0 && this._model.options.canvasInnerHeight > 0) { this._buffers = new MinimapBuffers( this._canvas.domNode.getContext('2d')!, - this._options.canvasInnerWidth, - this._options.canvasInnerHeight, - this._options.backgroundColor + this._model.options.canvasInnerWidth, + this._model.options.canvasInnerHeight, + this._model.options.backgroundColor ); } } return this._buffers ? this._buffers.getBuffer() : null; } - private _onOptionsMaybeChanged(): boolean { - const opts = new MinimapOptions(this._context.configuration, this._context.theme, this._tokensColorTracker); - if (this._options.equals(opts)) { - return false; - } - this._options = opts; + // ---- begin view event handlers + + public onDidChangeOptions(): void { this._lastRenderData = null; this._buffers = null; this._applyLayout(); this._domNode.setClassName(this._getMinimapDomNodeClassName()); - return true; } - - // ---- begin view event handlers - - public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - return this._onOptionsMaybeChanged(); - } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - this._selections = e.selections; + public onSelectionChanged(): boolean { this._renderDecorations = true; return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + public onDecorationsChanged(): boolean { + this._renderDecorations = true; + return true; + } + public onFlushed(): boolean { this._lastRenderData = null; return true; } - public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + public onLinesChanged(changeFromLineNumber: number, changeToLineNumber: number): boolean { if (this._lastRenderData) { - return this._lastRenderData.onLinesChanged(e); + return this._lastRenderData.onLinesChanged(changeFromLineNumber, changeToLineNumber); } return false; } - public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + public onLinesDeleted(deleteFromLineNumber: number, deleteToLineNumber: number): boolean { if (this._lastRenderData) { - this._lastRenderData.onLinesDeleted(e); + this._lastRenderData.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber); } return true; } - public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + public onLinesInserted(insertFromLineNumber: number, insertToLineNumber: number): boolean { if (this._lastRenderData) { - this._lastRenderData.onLinesInserted(e); + this._lastRenderData.onLinesInserted(insertFromLineNumber, insertToLineNumber); } return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + public onScrollChanged(): boolean { this._renderDecorations = true; return true; } - public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { + public onThemeChanged(): boolean { + this._selectionColor = this._theme.getColor(minimapSelection); + this._renderDecorations = true; + return true; + } + public onTokensChanged(ranges: { fromLineNumber: number; toLineNumber: number; }[]): boolean { if (this._lastRenderData) { - return this._lastRenderData.onTokensChanged(e); + return this._lastRenderData.onTokensChanged(ranges); } return false; } - public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean { + public onTokensColorsChanged(): boolean { this._lastRenderData = null; this._buffers = null; return true; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + public onZonesChanged(): boolean { this._lastRenderData = null; return true; } - public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { - this._renderDecorations = true; - return true; - } - - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { - this._context.model.invalidateMinimapColorCache(); - this._selectionColor = this._context.theme.getColor(minimapSelection); - this._renderDecorations = true; - this._onOptionsMaybeChanged(); - return true; - } - // --- end event handlers - public prepareRender(ctx: RenderingContext): void { - // Nothing to read - } - - public render(renderingCtx: RestrictedRenderingContext): void { - const renderMinimap = this._options.renderMinimap; + public render(renderingCtx: IMinimapRenderingContext): void { + const renderMinimap = this._model.options.renderMinimap; if (renderMinimap === RenderMinimap.None) { this._shadow.setClassName('minimap-shadow-hidden'); this._sliderHorizontal.setWidth(0); @@ -782,24 +1340,26 @@ export class Minimap extends ViewPart { } const layout = MinimapLayout.create( - this._options, - renderingCtx.visibleRange.startLineNumber, - renderingCtx.visibleRange.endLineNumber, + this._model.options, + renderingCtx.viewportStartLineNumber, + renderingCtx.viewportEndLineNumber, renderingCtx.viewportHeight, - (renderingCtx.viewportData.whitespaceViewportData.length > 0), - this._context.model.getLineCount(), + renderingCtx.viewportContainsWhitespaceGaps, + this._model.getLineCount(), + this._model.getRealLineCount(), renderingCtx.scrollTop, renderingCtx.scrollHeight, this._lastRenderData ? this._lastRenderData.renderedLayout : null ); + this._slider.setDisplay(layout.sliderNeeded ? 'block' : 'none'); this._slider.setTop(layout.sliderTop); this._slider.setHeight(layout.sliderHeight); // Compute horizontal slider coordinates - const scrollLeftChars = renderingCtx.scrollLeft / this._options.typicalHalfwidthCharacterWidth; - const horizontalSliderLeft = Math.min(this._options.minimapWidth, Math.round(scrollLeftChars * getMinimapCharWidth(this._options.renderMinimap, this._options.fontScale) / this._options.pixelRatio)); + const scrollLeftChars = renderingCtx.scrollLeft / this._model.options.typicalHalfwidthCharacterWidth; + const horizontalSliderLeft = Math.min(this._model.options.minimapWidth, Math.round(scrollLeftChars * this._model.options.minimapCharWidth / this._model.options.pixelRatio)); this._sliderHorizontal.setLeft(horizontalSliderLeft); - this._sliderHorizontal.setWidth(this._options.minimapWidth - horizontalSliderLeft); + this._sliderHorizontal.setWidth(this._model.options.minimapWidth - horizontalSliderLeft); this._sliderHorizontal.setTop(0); this._sliderHorizontal.setHeight(layout.sliderHeight); @@ -810,19 +1370,20 @@ export class Minimap extends ViewPart { private renderDecorations(layout: MinimapLayout) { if (this._renderDecorations) { this._renderDecorations = false; - const decorations = this._context.model.getDecorationsInViewport(new Range(layout.startLineNumber, 1, layout.endLineNumber, this._context.model.getLineMaxColumn(layout.endLineNumber))); + const selections = this._model.getSelections(); + const decorations = this._model.getMinimapDecorationsInViewport(layout.startLineNumber, layout.endLineNumber); - const { renderMinimap, canvasInnerWidth, canvasInnerHeight } = this._options; - const lineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); - const characterWidth = getMinimapCharWidth(renderMinimap, this._options.fontScale); - const tabSize = this._context.model.getOptions().tabSize; + const { canvasInnerWidth, canvasInnerHeight } = this._model.options; + const lineHeight = this._model.options.minimapLineHeight; + const characterWidth = this._model.options.minimapCharWidth; + const tabSize = this._model.getOptions().tabSize; const canvasContext = this._decorationsCanvas.domNode.getContext('2d')!; canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight); const lineOffsetMap = new Map(); - for (let i = 0; i < this._selections.length; i++) { - const selection = this._selections[i]; + for (let i = 0; i < selections.length; i++) { + const selection = selections[i]; for (let line = selection.startLineNumber; line <= selection.endLineNumber; line++) { this.renderDecorationOnLine(canvasContext, lineOffsetMap, selection, this._selectionColor, layout, line, lineHeight, lineHeight, tabSize, characterWidth); @@ -837,7 +1398,7 @@ export class Minimap extends ViewPart { continue; } - const decorationColor = (decoration.options.minimap).getColor(this._context.theme); + const decorationColor = (decoration.options.minimap).getColor(this._theme); for (let line = decoration.range.startLineNumber; line <= decoration.range.endLineNumber; line++) { switch (decoration.options.minimap.position) { @@ -869,7 +1430,7 @@ export class Minimap extends ViewPart { const y = (lineNumber - layout.startLineNumber) * lineHeight; // Skip rendering the line if it's vertically outside our viewport - if (y + height < 0 || y > this._options.canvasInnerHeight) { + if (y + height < 0 || y > this._model.options.canvasInnerHeight) { return; } @@ -877,7 +1438,7 @@ export class Minimap extends ViewPart { let lineIndexToXOffset = lineOffsetMap.get(lineNumber); const isFirstDecorationForLine = !lineIndexToXOffset; if (!lineIndexToXOffset) { - const lineData = this._context.model.getLineContent(lineNumber); + const lineData = this._model.getLineContent(lineNumber); lineIndexToXOffset = [MINIMAP_GUTTER_WIDTH]; for (let i = 1; i < lineData.length + 1; i++) { const charCode = lineData.charCodeAt(i - 1); @@ -922,11 +1483,9 @@ export class Minimap extends ViewPart { } private renderLines(layout: MinimapLayout): RenderData | null { - const renderMinimap = this._options.renderMinimap; - const charRenderer = this._options.charRenderer(); const startLineNumber = layout.startLineNumber; const endLineNumber = layout.endLineNumber; - const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); + const minimapLineHeight = this._model.options.minimapLineHeight; // Check if nothing changed w.r.t. lines from last frame if (this._lastRenderData && this._lastRenderData.linesEquals(layout)) { @@ -944,7 +1503,7 @@ export class Minimap extends ViewPart { } // Render untouched lines by using last rendered data. - let [_dirtyY1, _dirtyY2, needed] = Minimap._renderUntouchedLines( + let [_dirtyY1, _dirtyY2, needed] = InnerMinimap._renderUntouchedLines( imageData, startLineNumber, endLineNumber, @@ -953,27 +1512,39 @@ export class Minimap extends ViewPart { ); // Fetch rendering info from view model for rest of lines that need rendering. - const lineInfo = this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); - const tabSize = lineInfo.tabSize; - const background = this._options.backgroundColor; - const useLighterFont = this._tokensColorTracker.backgroundIsLight(); + const lineInfo = this._model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); + const tabSize = this._model.getOptions().tabSize; + const background = this._model.options.backgroundColor; + const tokensColorTracker = this._model.tokensColorTracker; + const useLighterFont = tokensColorTracker.backgroundIsLight(); + const renderMinimap = this._model.options.renderMinimap; + const charRenderer = this._model.options.charRenderer(); + const fontScale = this._model.options.fontScale; + const minimapCharWidth = this._model.options.minimapCharWidth; + + const baseCharHeight = (renderMinimap === RenderMinimap.Text ? Constants.BASE_CHAR_HEIGHT : Constants.BASE_CHAR_HEIGHT + 1); + const renderMinimapLineHeight = baseCharHeight * fontScale; + const innerLinePadding = (minimapLineHeight > renderMinimapLineHeight ? Math.floor((minimapLineHeight - renderMinimapLineHeight) / 2) : 0); // Render the rest of lines let dy = 0; const renderedLines: MinimapLine[] = []; for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) { if (needed[lineIndex]) { - Minimap._renderLine( + InnerMinimap._renderLine( imageData, background, useLighterFont, renderMinimap, - this._tokensColorTracker, + minimapCharWidth, + tokensColorTracker, charRenderer, dy, + innerLinePadding, tabSize, - lineInfo.data[lineIndex]!, - this._options.fontScale + lineInfo[lineIndex]!, + fontScale, + minimapLineHeight ); } renderedLines[lineIndex] = new MinimapLine(dy); @@ -1093,17 +1664,20 @@ export class Minimap extends ViewPart { backgroundColor: RGBA8, useLighterFont: boolean, renderMinimap: RenderMinimap, + charWidth: number, colorTracker: MinimapTokensColorTracker, minimapCharRenderer: MinimapCharRenderer, dy: number, + innerLinePadding: number, tabSize: number, lineData: ViewLineData, - fontScale: number + fontScale: number, + minimapLineHeight: number ): void { const content = lineData.content; const tokens = lineData.tokens; - const charWidth = getMinimapCharWidth(renderMinimap, fontScale); const maxDx = target.width - charWidth; + const force1pxHeight = (minimapLineHeight === 1); let dx = MINIMAP_GUTTER_WIDTH; let charIndex = 0; @@ -1135,9 +1709,9 @@ export class Minimap extends ViewPart { for (let i = 0; i < count; i++) { if (renderMinimap === RenderMinimap.Blocks) { - minimapCharRenderer.blockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); + minimapCharRenderer.blockRenderChar(target, dx, dy + innerLinePadding, tokenColor, backgroundColor, useLighterFont, force1pxHeight); } else { // RenderMinimap.Text - minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, fontScale, useLighterFont); + minimapCharRenderer.renderChar(target, dx, dy + innerLinePadding, charCode, tokenColor, backgroundColor, fontScale, useLighterFont, force1pxHeight); } dx += charWidth; @@ -1158,20 +1732,17 @@ registerThemingParticipant((theme, collector) => { if (minimapBackgroundValue) { collector.addRule(`.monaco-editor .minimap > canvas { opacity: ${minimapBackgroundValue.rgba.a}; will-change: opacity; }`); } - const sliderBackground = theme.getColor(scrollbarSliderBackground); + const sliderBackground = theme.getColor(minimapSliderBackground); if (sliderBackground) { - const halfSliderBackground = sliderBackground.transparent(0.5); - collector.addRule(`.monaco-editor .minimap-slider, .monaco-editor .minimap-slider .minimap-slider-horizontal { background: ${halfSliderBackground}; }`); + collector.addRule(`.monaco-editor .minimap-slider .minimap-slider-horizontal { background: ${sliderBackground}; }`); } - const sliderHoverBackground = theme.getColor(scrollbarSliderHoverBackground); + const sliderHoverBackground = theme.getColor(minimapSliderHoverBackground); if (sliderHoverBackground) { - const halfSliderHoverBackground = sliderHoverBackground.transparent(0.5); - collector.addRule(`.monaco-editor .minimap-slider:hover, .monaco-editor .minimap-slider:hover .minimap-slider-horizontal { background: ${halfSliderHoverBackground}; }`); + collector.addRule(`.monaco-editor .minimap-slider:hover .minimap-slider-horizontal { background: ${sliderHoverBackground}; }`); } - const sliderActiveBackground = theme.getColor(scrollbarSliderActiveBackground); + const sliderActiveBackground = theme.getColor(minimapSliderActiveBackground); if (sliderActiveBackground) { - const halfSliderActiveBackground = sliderActiveBackground.transparent(0.5); - collector.addRule(`.monaco-editor .minimap-slider.active, .monaco-editor .minimap-slider.active .minimap-slider-horizontal { background: ${halfSliderActiveBackground}; }`); + collector.addRule(`.monaco-editor .minimap-slider.active .minimap-slider-horizontal { background: ${sliderActiveBackground}; }`); } const shadow = theme.getColor(scrollbarShadow); if (shadow) { diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts index 6feeecc03ac..c30b720c207 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -34,11 +34,13 @@ export class MinimapCharRenderer { color: RGBA8, backgroundColor: RGBA8, fontScale: number, - useLighterFont: boolean + useLighterFont: boolean, + force1pxHeight: boolean ): void { const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; - if (dx + charWidth > target.width || dy + charHeight > target.height) { + const renderHeight = (force1pxHeight ? 1 : charHeight); + if (dx + charWidth > target.width || dy + renderHeight > target.height) { console.warn('bad render request outside image data'); return; } @@ -60,7 +62,7 @@ export class MinimapCharRenderer { let sourceOffset = charIndex * charWidth * charHeight; let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; - for (let y = 0; y < charHeight; y++) { + for (let y = 0; y < renderHeight; y++) { let column = row; for (let x = 0; x < charWidth; x++) { const c = charData[sourceOffset++] / 255; @@ -80,11 +82,13 @@ export class MinimapCharRenderer { dy: number, color: RGBA8, backgroundColor: RGBA8, - useLighterFont: boolean + useLighterFont: boolean, + force1pxHeight: boolean ): void { const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; - if (dx + charWidth > target.width || dy + charHeight > target.height) { + const renderHeight = (force1pxHeight ? 1 : charHeight); + if (dx + charWidth > target.width || dy + renderHeight > target.height) { console.warn('bad render request outside image data'); return; } @@ -108,7 +112,7 @@ export class MinimapCharRenderer { const dest = target.data; let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; - for (let y = 0; y < charHeight; y++) { + for (let y = 0; y < renderHeight; y++) { let column = row; for (let x = 0; x < charWidth; x++) { dest[column++] = colorR; diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 0cbac3c5035..bd1aa10f8a8 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -276,7 +276,10 @@ export class DecorationsOverviewRuler extends ViewPart { return true; } public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { - return true; + if (e.affectsOverviewRuler) { + return true; + } + return false; } public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; 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 793ebe48c2e..cb0c34760f3 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -32,7 +32,7 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { InternalEditorAction } from 'vs/editor/common/editorAction'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; +import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, ICursorStateComputer, IWordAtPosition } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; @@ -52,6 +52,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { withNullAsUndefined } from 'vs/base/common/types'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; +import { WordOperations } from 'vs/editor/common/controller/cursorWordOperations'; let EDITOR_ID = 0; @@ -376,6 +377,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._configuration.getRawOptions(); } + public getConfiguredWordAtPosition(position: Position): IWordAtPosition | null { + if (!this._modelData) { + return null; + } + return WordOperations.getWordAtPosition(this._modelData.model, this._configuration.options.get(EditorOption.wordSeparators), position); + } + public getValue(options: { preserveBOM: boolean; lineEnding: string; } | null = null): string { if (!this._modelData) { return ''; @@ -452,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 []; @@ -542,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 { @@ -756,6 +771,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } + public revealRangeNearTopIfOutsideViewport(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealRange( + range, + VerticalRevealType.NearTopIfOutsideViewport, + true, + scrollType + ); + } + public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this._revealRange( range, @@ -1534,11 +1558,18 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE }; } + const onDidChangeTextFocus = (textFocus: boolean) => { + if (this._modelData) { + this._modelData.cursor.setHasFocus(textFocus); + } + this._editorTextFocus.setValue(textFocus); + }; + const viewOutgoingEvents = new ViewOutgoingEvents(viewModel); viewOutgoingEvents.onDidContentSizeChange = (e) => this._onDidContentSizeChange.fire(e); viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e); - viewOutgoingEvents.onDidGainFocus = () => this._editorTextFocus.setValue(true); - viewOutgoingEvents.onDidLoseFocus = () => this._editorTextFocus.setValue(false); + viewOutgoingEvents.onDidGainFocus = () => onDidChangeTextFocus(true); + viewOutgoingEvents.onDidLoseFocus = () => onDidChangeTextFocus(false); viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e); viewOutgoingEvents.onMouseDown = (e) => this._onMouseDown.fire(e); viewOutgoingEvents.onMouseUp = (e) => this._onMouseUp.fire(e); @@ -1579,7 +1610,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData = null; this._domElement.removeAttribute('data-mode-id'); - if (removeDomNode) { + if (removeDomNode && this._domElement.contains(removeDomNode)) { this._domElement.removeChild(removeDomNode); } @@ -1650,6 +1681,7 @@ class EditorContextKeysManager extends Disposable { private readonly _editorTextFocus: IContextKey; private readonly _editorTabMovesFocus: IContextKey; private readonly _editorReadonly: IContextKey; + private readonly _editorColumnSelection: IContextKey; private readonly _hasMultipleSelections: IContextKey; private readonly _hasNonEmptySelection: IContextKey; private readonly _canUndo: IContextKey; @@ -1671,6 +1703,7 @@ class EditorContextKeysManager extends Disposable { this._editorTextFocus = EditorContextKeys.editorTextFocus.bindTo(contextKeyService); this._editorTabMovesFocus = EditorContextKeys.tabMovesFocus.bindTo(contextKeyService); this._editorReadonly = EditorContextKeys.readOnly.bindTo(contextKeyService); + this._editorColumnSelection = EditorContextKeys.columnSelection.bindTo(contextKeyService); this._hasMultipleSelections = EditorContextKeys.hasMultipleSelections.bindTo(contextKeyService); this._hasNonEmptySelection = EditorContextKeys.hasNonEmptySelection.bindTo(contextKeyService); this._canUndo = EditorContextKeys.canUndo.bindTo(contextKeyService); @@ -1698,6 +1731,7 @@ class EditorContextKeysManager extends Disposable { this._editorTabMovesFocus.set(options.get(EditorOption.tabFocusMode)); this._editorReadonly.set(options.get(EditorOption.readOnly)); + this._editorColumnSelection.set(options.get(EditorOption.columnSelection)); } private _updateFromSelection(): void { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 366335f5fa5..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; } @@ -84,7 +84,7 @@ class VisualEditorState { constructor( private _contextMenuService: IContextMenuService, - private _clipboardService: IClipboardService | null + private _clipboardService: IClipboardService ) { this._zones = []; this.inlineDiffMargins = []; @@ -134,7 +134,7 @@ class VisualEditorState { this._zones.push(zoneId); this._zonesMap[String(zoneId)] = true; - if (newDecorations.zones[i].diff && viewZone.marginDomNode && this._clipboardService) { + if (newDecorations.zones[i].diff && viewZone.marginDomNode) { viewZone.suppressMouseDown = false; this.inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService)); } @@ -220,7 +220,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE constructor( domElement: HTMLElement, options: IDiffEditorOptions, - clipboardService: IClipboardService | null, + @IClipboardService clipboardService: IClipboardService, @IEditorWorkerService editorWorkerService: IEditorWorkerService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, @@ -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); } } @@ -825,6 +825,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealRangeNearTop(range, scrollType); } + public revealRangeNearTopIfOutsideViewport(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealRangeNearTopIfOutsideViewport(range, scrollType); + } + public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this.modifiedEditor.revealRangeAtTop(range, scrollType); } @@ -1168,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(); @@ -1292,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 cfa24de682f..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; @@ -98,7 +99,7 @@ export class DiffReview extends Disposable { this.actionBarContainer.domNode )); - this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review', true, () => { + this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review codicon-close', true, () => { this.hide(); return Promise.resolve(null); }), { label: false, icon: true }); @@ -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,19 +604,20 @@ 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 @@ header.setAttribute('role', 'listitem'); container.appendChild(header); + const lineHeight = modifiedOptions.get(EditorOption.lineHeight); let modLine = minModifiedLine; for (let i = 0, len = diffs.length; i < len; i++) { const diffEntry = diffs[i]; - DiffReview._renderSection(container, diffEntry, modLine, this._width, originalOptions, originalModel, originalModelOpts, modifiedOptions, modifiedModel, modifiedModelOpts); + DiffReview._renderSection(container, diffEntry, modLine, lineHeight, this._width, originalOptions, originalModel, originalModelOpts, modifiedOptions, modifiedModel, modifiedModelOpts); if (diffEntry.modifiedLineStart !== 0) { modLine = diffEntry.modifiedLineEnd; } @@ -632,7 +629,7 @@ export class DiffReview extends Disposable { } private static _renderSection( - dest: HTMLElement, diffEntry: DiffEntry, modLine: number, width: number, + dest: HTMLElement, diffEntry: DiffEntry, modLine: number, lineHeight: number, width: number, originalOptions: IComputedEditorOptions, originalModel: ITextModel, originalModelOpts: TextModelResolvedOptions, modifiedOptions: IComputedEditorOptions, modifiedModel: ITextModel, modifiedModelOpts: TextModelResolvedOptions ): void { @@ -641,17 +638,18 @@ export class DiffReview extends Disposable { let rowClassName: string = 'diff-review-row'; let lineNumbersExtraClassName: string = ''; - let spacerClassName: string = 'diff-review-spacer'; + const spacerClassName: string = 'diff-review-spacer'; + let spacerCodiconName: string | null = null; switch (type) { case DiffEntryType.Insert: rowClassName = 'diff-review-row line-insert'; lineNumbersExtraClassName = ' char-insert'; - spacerClassName = 'diff-review-spacer insert-sign'; + spacerCodiconName = 'codicon codicon-add'; break; case DiffEntryType.Delete: rowClassName = 'diff-review-row line-delete'; lineNumbersExtraClassName = ' char-delete'; - spacerClassName = 'diff-review-spacer delete-sign'; + spacerCodiconName = 'codicon codicon-remove'; break; } @@ -686,6 +684,7 @@ export class DiffReview extends Disposable { let cell = document.createElement('div'); cell.className = 'diff-review-cell'; + cell.style.height = `${lineHeight}px`; row.appendChild(cell); const originalLineNumber = document.createElement('span'); @@ -713,7 +712,15 @@ export class DiffReview extends Disposable { const spacer = document.createElement('span'); spacer.className = spacerClassName; - spacer.innerHTML = '  '; + + if (spacerCodiconName) { + const spacerCodicon = document.createElement('span'); + spacerCodicon.className = spacerCodiconName; + spacerCodicon.innerHTML = '  '; + spacer.appendChild(spacerCodicon); + } else { + spacer.innerHTML = '  '; + } cell.appendChild(spacer); let lineContent: string; @@ -736,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); @@ -858,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/browser/widget/media/diffReview.css b/src/vs/editor/browser/widget/media/diffReview.css index b2b17028489..56c6f15cbf6 100644 --- a/src/vs/editor/browser/widget/media/diffReview.css +++ b/src/vs/editor/browser/widget/media/diffReview.css @@ -37,13 +37,14 @@ width: 100%; } -.monaco-diff-editor .diff-review-cell { - display: table-cell; -} - .monaco-diff-editor .diff-review-spacer { display: inline-block; width: 10px; + vertical-align: middle; +} + +.monaco-diff-editor .diff-review-spacer > .codicon { + font-size: 9px !important; } .monaco-diff-editor .diff-review-actions { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index ed8c20c1674..ee224f1f6fd 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -287,6 +287,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC public options!: ComputedEditorOptions; private _isDominatedByLongLines: boolean; + private _viewLineCount: number; private _lineNumbersDigitCount: number; private _rawOptions: IEditorOptions; @@ -298,6 +299,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC this.isSimpleWidget = isSimpleWidget; this._isDominatedByLongLines = false; + this._viewLineCount = 1; this._lineNumbersDigitCount = 1; this._rawOptions = deepCloneAndMigrateOptions(_options); @@ -347,6 +349,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC fontInfo: this.readConfiguration(bareFontInfo), extraEditorClassName: partialEnv.extraEditorClassName, isDominatedByLongLines: this._isDominatedByLongLines, + viewLineCount: this._viewLineCount, lineNumbersDigitCount: this._lineNumbersDigitCount, emptySelectionClipboard: partialEnv.emptySelectionClipboard, pixelRatio: partialEnv.pixelRatio, @@ -405,11 +408,19 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC } public setMaxLineNumber(maxLineNumber: number): void { - let digitCount = CommonEditorConfiguration._digitCount(maxLineNumber); - if (this._lineNumbersDigitCount === digitCount) { + const lineNumbersDigitCount = CommonEditorConfiguration._digitCount(maxLineNumber); + if (this._lineNumbersDigitCount === lineNumbersDigitCount) { return; } - this._lineNumbersDigitCount = digitCount; + this._lineNumbersDigitCount = lineNumbersDigitCount; + this._recomputeOptions(); + } + + public setViewLineCount(viewLineCount: number): void { + if (this._viewLineCount === viewLineCount) { + return; + } + this._viewLineCount = viewLineCount; this._recomputeOptions(); } @@ -512,7 +523,7 @@ const editorConfiguration: IConfigurationNode = { 'diffEditor.ignoreTrimWhitespace': { type: 'boolean', default: true, - description: nls.localize('ignoreTrimWhitespace', "Controls whether the diff editor shows changes in leading or trailing whitespace as diffs.") + description: nls.localize('ignoreTrimWhitespace', "When enabled, the diff editor ignores changes in leading or trailing whitespace.") }, 'diffEditor.renderIndicators': { type: 'boolean', diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 50d6dd6c3e0..03354b46bce 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. @@ -324,6 +329,11 @@ export interface IEditorOptions { * Defaults to true. */ scrollPredominantAxis?: boolean; + /** + * Enable that the selection with the mouse and keys is doing column selection. + * Defaults to false. + */ + columnSelection?: boolean; /** * The modifier to be used to add multiple cursors with the mouse. * Defaults to 'alt' @@ -501,6 +511,11 @@ export interface IEditorOptions { * Defaults to 'mouseover'. */ showFoldingControls?: 'always' | 'mouseover'; + /** + * Controls whether clicking on the empty content after a folded line will unfold the line. + * Defaults to false. + */ + unfoldOnClickAfterEndOfLine?: boolean; /** * Enable highlighting of matching brackets. * Defaults to 'always'. @@ -531,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. */ @@ -672,6 +692,7 @@ export interface IEnvironmentalOptions { readonly fontInfo: FontInfo; readonly extraEditorClassName: string; readonly isDominatedByLongLines: boolean; + readonly viewLineCount: number; readonly lineNumbersDigitCount: number; readonly emptySelectionClipboard: boolean; readonly pixelRatio: number; @@ -1232,6 +1253,10 @@ export interface IEditorFindOptions { * Controls if the Find Widget should read or modify the shared find clipboard on macOS */ globalFindClipboard?: boolean; + /** + * Controls whether the search automatically restarts from the beginning (or the end) when no further matches can be found + */ + loop?: boolean; } export type EditorFindOptions = Readonly>; @@ -1243,7 +1268,8 @@ class EditorFind extends BaseEditorOption seedSearchStringFromSelection: true, autoFindInSelection: 'never', globalFindClipboard: false, - addExtraSpaceOnTop: true + addExtraSpaceOnTop: true, + loop: true }; super( EditorOption.find, 'find', defaults, @@ -1274,7 +1300,13 @@ class EditorFind extends BaseEditorOption type: 'boolean', default: defaults.addExtraSpaceOnTop, description: nls.localize('find.addExtraSpaceOnTop', "Controls whether the Find Widget should add extra lines on top of the editor. When true, you can scroll beyond the first line when the Find Widget is visible.") - } + }, + 'editor.find.loop': { + type: 'boolean', + default: defaults.loop, + description: nls.localize('find.loop', "Controls whether the search automatically restarts from the beginning (or the end) when no further matches can be found.") + }, + } ); } @@ -1290,7 +1322,8 @@ class EditorFind extends BaseEditorOption ? (_input.autoFindInSelection ? 'always' : 'never') : EditorStringEnumOption.stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']), globalFindClipboard: EditorBooleanOption.boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard), - addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop) + addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop), + loop: EditorBooleanOption.boolean(input.loop, this.defaultValue.loop), }; } } @@ -1321,7 +1354,7 @@ export class EditorFontLigatures extends BaseEditorOption= 2 ? Math.round(minimap.scale * 2) : minimap.scale); + let minimapScale = (pixelRatio >= 2 ? Math.round(minimap.scale * 2) : minimap.scale); const minimapMaxColumn = minimap.maxColumn | 0; + const minimapSize = minimap.size; const scrollbar = options.get(EditorOption.scrollbar); const verticalScrollbarWidth = scrollbar.verticalScrollbarSize | 0; @@ -1805,19 +1864,65 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption 1) { + minimapHeightIsEditorHeight = true; + minimapIsSampling = true; + minimapScale = 1; + minimapLineHeight = 1; + minimapCharWidth = minimapScale / pixelRatio; + } else { + const effectiveMinimapHeight = Math.ceil((viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight); + if (minimapSize === 'fill' || effectiveMinimapHeight > minimapCanvasInnerHeight) { + minimapHeightIsEditorHeight = true; + const configuredFontScale = minimapScale; + minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio))); + minimapScale = Math.min(configuredFontScale + 1, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); + if (minimapScale > configuredFontScale) { + minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale); + } + minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; + minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, viewLineCount + extraLinesBeyondLastLine)) * minimapLineHeight); + } + } + } + renderMinimap = minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks; // Given: @@ -1849,6 +1954,10 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption(input.size, this.defaultValue.size, ['proportional', 'fill', 'fit']), side: EditorStringEnumOption.stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']), showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']), renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters), @@ -2593,10 +2728,6 @@ export interface ISuggestOptions { * 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. */ @@ -2717,14 +2848,27 @@ export interface ISuggestOptions { * Show typeParameter-suggestions. */ showTypeParameters?: boolean; + /** + * Show issue-suggestions. + */ + showIssues?: boolean; + /** + * Show user-suggestions. + */ + showUsers?: boolean; /** * Show snippet-suggestions. */ showSnippets?: boolean; /** - * Controls the visibility of the status bar at the bottom of the suggest widget. + * Status bar related settings. */ - hideStatusBar?: boolean; + statusBar?: { + /** + * Controls the visibility of the status bar at the bottom of the suggest widget. + */ + visible?: boolean; + } } export type InternalSuggestOptions = Readonly>; @@ -2734,7 +2878,6 @@ class EditorSuggest extends BaseEditorOption { @@ -264,6 +265,10 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { super.dispose(); } + public setHasFocus(hasFocus: boolean): void { + this._hasFocus = hasFocus; + } + private _validateAutoClosedActions(): void { if (this._autoClosedActions.length > 0) { let selections: Range[] = this._cursors.getSelections(); @@ -292,7 +297,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { return this._cursors.getAll(); } - public setStates(source: string, reason: CursorChangeReason, states: PartialCursorState[] | null): void { + public setStates(source: string, reason: CursorChangeReason, states: PartialCursorState[] | null): boolean { if (states !== null && states.length > Cursor.MAX_CURSOR_COUNT) { states = states.slice(0, Cursor.MAX_CURSOR_COUNT); this._onDidReachMaxCursorCount.fire(undefined); @@ -306,7 +311,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._validateAutoClosedActions(); - this._emitStateChangedIfNecessary(source, reason, oldState); + return this._emitStateChangedIfNecessary(source, reason, oldState); } public setColumnSelectData(columnSelectData: IColumnSelectData): void { @@ -318,7 +323,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { } public revealRange(source: string, revealHorizontal: boolean, viewRange: Range, verticalType: viewEvents.VerticalRevealType, scrollType: editorCommon.ScrollType) { - this.emitCursorRevealRange(source, viewRange, verticalType, revealHorizontal, scrollType); + this.emitCursorRevealRange(source, viewRange, null, verticalType, revealHorizontal, scrollType); } public scrollTo(desiredScrollTop: number): void { @@ -392,8 +397,9 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this.reveal('restoreState', true, RevealTarget.Primary, editorCommon.ScrollType.Immediate); } - private _onModelContentChanged(hadFlushEvent: boolean): void { + private _onModelContentChanged(e: ModelRawContentChangedEvent): void { + const hadFlushEvent = e.containsEvent(RawContentChangedType.Flush); this._prevEditOperationType = EditOperationType.Other; if (hadFlushEvent) { @@ -403,8 +409,15 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._validateAutoClosedActions(); this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null); } else { - const selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); - this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers)); + if (this._hasFocus && e.resultingSelection && e.resultingSelection.length > 0) { + const cursorState = CursorState.fromModelSelections(e.resultingSelection); + if (this.setStates('modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState)) { + this._revealRange('modelChange', RevealTarget.Primary, viewEvents.VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth); + } + } else { + const selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); + this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers)); + } } } @@ -417,15 +430,14 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { return this._columnSelectData; } const primaryCursor = this._cursors.getPrimaryCursor(); - const primaryPos = primaryCursor.viewState.selectionStart.getStartPosition(); - const viewLineNumber = primaryPos.lineNumber; - const viewVisualColumn = CursorColumns.visibleColumnFromColumn2(this.context.config, this.context.viewModel, primaryPos); + const viewSelectionStart = primaryCursor.viewState.selectionStart.getStartPosition(); + const viewPosition = primaryCursor.viewState.position; return { isReal: false, - fromViewLineNumber: viewLineNumber, - fromViewVisualColumn: viewVisualColumn, - toViewLineNumber: viewLineNumber, - toViewVisualColumn: viewVisualColumn, + fromViewLineNumber: viewSelectionStart.lineNumber, + fromViewVisualColumn: CursorColumns.visibleColumnFromColumn2(this.context.config, this.context.viewModel, viewSelectionStart), + toViewLineNumber: viewPosition.lineNumber, + toViewVisualColumn: CursorColumns.visibleColumnFromColumn2(this.context.config, this.context.viewModel, viewPosition), }; } @@ -582,19 +594,19 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { } } else { if (viewPositions.length > 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(); } @@ -704,11 +716,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { const oldState = new CursorModelState(this._model, this); let cursorChangeReason = CursorChangeReason.NotSet; - if (handlerId !== H.Undo && handlerId !== H.Redo) { - // TODO@Alex: if the undo/redo stack contains non-null selections - // it would also be OK to stop tracking selections here - this._cursors.stopTrackingSelections(); - } + this._cursors.stopTrackingSelections(); // ensure valid state on all cursors this._cursors.ensureValidState(); @@ -734,16 +742,6 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._cut(); break; - case H.Undo: - cursorChangeReason = CursorChangeReason.Undo; - this._interpretCommandResult(this._model.undo()); - break; - - case H.Redo: - cursorChangeReason = CursorChangeReason.Redo; - this._interpretCommandResult(this._model.redo()); - break; - case H.ExecuteCommand: this._externalExecuteCommand(payload); break; @@ -762,9 +760,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._isHandling = false; - if (handlerId !== H.Undo && handlerId !== H.Redo) { - this._cursors.startTrackingSelections(); - } + this._cursors.startTrackingSelections(); this._validateAutoClosedActions(); @@ -783,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; @@ -794,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; } @@ -903,8 +899,8 @@ class CommandExecutor { if (commandsData.hadTrackedEditOperation && filteredOperations.length > 0) { filteredOperations[0]._isTracked = true; } - let selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] => { - let groupedInverseEditOperations: IIdentifiedSingleEditOperation[][] = []; + let selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: IValidEditOperation[]): Selection[] => { + let groupedInverseEditOperations: IValidEditOperation[][] = []; for (let i = 0; i < ctx.selectionsBefore.length; i++) { groupedInverseEditOperations[i] = []; } @@ -915,7 +911,7 @@ class CommandExecutor { } groupedInverseEditOperations[op.identifier.major].push(op); } - const minorBasedSorter = (a: IIdentifiedSingleEditOperation, b: IIdentifiedSingleEditOperation) => { + const minorBasedSorter = (a: IValidEditOperation, b: IValidEditOperation) => { return a.identifier!.minor - b.identifier!.minor; }; let cursorSelections: Selection[] = []; @@ -1000,8 +996,8 @@ class CommandExecutor { let operations: IIdentifiedSingleEditOperation[] = []; let operationMinor = 0; - const addEditOperation = (selection: Range, text: string | null, forceMoveMarkers: boolean = false) => { - if (selection.isEmpty() && text === '') { + const addEditOperation = (range: IRange, text: string | null, forceMoveMarkers: boolean = false) => { + if (Range.isEmpty(range) && text === '') { // This command wants to add a no-op => no thank you return; } @@ -1010,7 +1006,7 @@ class CommandExecutor { major: majorIdentifier, minor: operationMinor++ }, - range: selection, + range: range, text: text, forceMoveMarkers: forceMoveMarkers, isAutoWhitespaceEdit: command.insertsAutoWhitespace @@ -1018,12 +1014,13 @@ class CommandExecutor { }; let hadTrackedEditOperation = false; - const addTrackedEditOperation = (selection: Range, text: string | null, forceMoveMarkers?: boolean) => { + const addTrackedEditOperation = (selection: IRange, text: string | null, forceMoveMarkers?: boolean) => { hadTrackedEditOperation = true; addEditOperation(selection, text, forceMoveMarkers); }; - const trackSelection = (selection: Selection, trackPreviousOnEmpty?: boolean) => { + const trackSelection = (_selection: ISelection, trackPreviousOnEmpty?: boolean) => { + const selection = Selection.liftSelection(_selection); let stickiness: TrackedRangeStickiness; if (selection.isEmpty()) { if (typeof trackPreviousOnEmpty === 'boolean') { @@ -1093,7 +1090,7 @@ class CommandExecutor { const previousOp = operations[i - 1]; const currentOp = operations[i]; - if (previousOp.range.getStartPosition().isBefore(currentOp.range.getEndPosition())) { + if (Range.getStartPosition(previousOp.range).isBefore(Range.getEndPosition(currentOp.range))) { let loserMajor: number; 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 84a9dcd96d2..13acacc4cbe 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -10,6 +10,7 @@ import { WordCharacterClass, WordCharacterClassifier, getMapForWordSeparators } 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 { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; interface IFindWordResult { /** @@ -237,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); } @@ -339,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); } @@ -535,6 +536,28 @@ export class WordOperations { return new Range(pos.lineNumber, pos.column, toPosition.lineNumber, toPosition.column); } + private static _createWordAtPosition(model: ITextModel, lineNumber: number, word: IFindWordResult): IWordAtPosition { + const range = new Range(lineNumber, word.start + 1, lineNumber, word.end + 1); + return { + word: model.getValueInRange(range), + startColumn: range.startColumn, + endColumn: range.endColumn + }; + } + + public static getWordAtPosition(model: ITextModel, _wordSeparators: string, position: Position): IWordAtPosition | null { + const wordSeparators = getMapForWordSeparators(_wordSeparators); + const prevWord = WordOperations._findPreviousWordOnLine(wordSeparators, model, position); + if (prevWord && prevWord.wordType === WordType.Regular && prevWord.start <= position.column - 1 && position.column - 1 <= prevWord.end) { + return WordOperations._createWordAtPosition(model, position.lineNumber, prevWord); + } + const nextWord = WordOperations._findNextWordOnLine(wordSeparators, model, position); + if (nextWord && nextWord.wordType === WordType.Regular && nextWord.start <= position.column - 1 && position.column - 1 <= nextWord.end) { + return WordOperations._createWordAtPosition(model, position.lineNumber, nextWord); + } + return null; + } + public static word(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, position: Position): SingleCursorState { const wordSeparators = getMapForWordSeparators(config.wordSeparators); let prevWord = WordOperations._findPreviousWordOnLine(wordSeparators, model, position); diff --git a/src/vs/editor/common/core/range.ts b/src/vs/editor/common/core/range.ts index e212e757dce..d684e3b9520 100644 --- a/src/vs/editor/common/core/range.ts +++ b/src/vs/editor/common/core/range.ts @@ -264,14 +264,28 @@ export class Range { * Return the end position (which will be after or equal to the start position) */ public getEndPosition(): Position { - return new Position(this.endLineNumber, this.endColumn); + return Range.getEndPosition(this); + } + + /** + * Return the end position (which will be after or equal to the start position) + */ + public static getEndPosition(range: IRange): Position { + return new Position(range.endLineNumber, range.endColumn); } /** * Return the start position (which will be before or equal to the end position) */ public getStartPosition(): Position { - return new Position(this.startLineNumber, this.startColumn); + return Range.getStartPosition(this); + } + + /** + * Return the start position (which will be before or equal to the end position) + */ + public static getStartPosition(range: IRange): Position { + return new Position(range.startLineNumber, range.startColumn); } /** 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/editorAction.ts b/src/vs/editor/common/editorAction.ts index 7c4ee116356..d8a1e71ade2 100644 --- a/src/vs/editor/common/editorAction.ts +++ b/src/vs/editor/common/editorAction.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEditorAction } from 'vs/editor/common/editorCommon'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class InternalEditorAction implements IEditorAction { @@ -12,7 +12,7 @@ export class InternalEditorAction implements IEditorAction { public readonly label: string; public readonly alias: string; - private readonly _precondition: ContextKeyExpr | undefined; + private readonly _precondition: ContextKeyExpression | undefined; private readonly _run: () => Promise; private readonly _contextKeyService: IContextKeyService; @@ -20,7 +20,7 @@ export class InternalEditorAction implements IEditorAction { id: string, label: string, alias: string, - precondition: ContextKeyExpr | undefined, + precondition: ContextKeyExpression | undefined, run: () => Promise, contextKeyService: IContextKeyService ) { diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 426b028ed3d..b236bb7fe7e 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -5,12 +5,13 @@ 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'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { IIdentifiedSingleEditOperation, IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness, IValidEditOperation } from 'vs/editor/common/model'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; /** @@ -22,7 +23,7 @@ export interface IEditOperationBuilder { * @param range The range to replace (delete). May be empty to represent a simple insert. * @param text The text to replace with. May be null to represent a simple delete. */ - addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; + addEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void; /** * Add a new edit operation (a replace operation). @@ -30,7 +31,7 @@ export interface IEditOperationBuilder { * @param range The range to replace (delete). May be empty to represent a simple insert. * @param text The text to replace with. May be null to represent a simple delete. */ - addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; + addTrackedEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void; /** * Track `selection` when applying edit operations. @@ -51,7 +52,7 @@ export interface ICursorStateComputerData { /** * Get the inverse edit operations of the added edit operations. */ - getInverseEditOperations(): IIdentifiedSingleEditOperation[]; + getInverseEditOperations(): IValidEditOperation[]; /** * Get a previously tracked selection. * @param id The unique identifier returned by `trackSelection`. @@ -154,6 +155,7 @@ export interface IConfiguration extends IDisposable { readonly options: IComputedEditorOptions; setMaxLineNumber(maxLineNumber: number): void; + setViewLineCount(viewLineCount: number): void; updateOptions(newOptions: IEditorOptions): void; getRawOptions(): IEditorOptions; observeReferenceElement(dimension?: IDimension): void; @@ -466,6 +468,12 @@ export interface IEditor { */ revealRangeNearTop(range: IRange, scrollType?: ScrollType): void; + /** + * Scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, + * optimized for viewing a code definition. Only if it lies outside the viewport. + */ + revealRangeNearTopIfOutsideViewport(range: IRange, scrollType?: ScrollType): void; + /** * Directly trigger a handler or an editor action. * @param source The source of the call. @@ -522,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. */ @@ -671,9 +697,5 @@ export const Handler = { CompositionStart: 'compositionStart', CompositionEnd: 'compositionEnd', Paste: 'paste', - Cut: 'cut', - - Undo: 'undo', - Redo: 'redo', }; diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index f15b8091073..29093021ed5 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -3,13 +3,14 @@ * 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 { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export namespace EditorContextKeys { export const editorSimpleInput = new RawContextKey('editorSimpleInput', false); /** * A context key that is set when the editor's text has focus (cursor is blinking). + * Is false when focus is in simple editor widgets (repl input, scm commit input). */ export const editorTextFocus = new RawContextKey('editorTextFocus', false); /** @@ -23,13 +24,14 @@ export namespace EditorContextKeys { export const textInputFocus = new RawContextKey('textInputFocus', false); export const readOnly = new RawContextKey('editorReadonly', false); - export const writable: ContextKeyExpr = readOnly.toNegated(); + export const columnSelection = new RawContextKey('editorColumnSelection', false); + export const writable = readOnly.toNegated(); export const hasNonEmptySelection = new RawContextKey('editorHasSelection', false); - export const hasOnlyEmptySelection: ContextKeyExpr = hasNonEmptySelection.toNegated(); + export const hasOnlyEmptySelection = hasNonEmptySelection.toNegated(); export const hasMultipleSelections = new RawContextKey('editorHasMultipleSelections', false); - export const hasSingleSelection: ContextKeyExpr = hasMultipleSelections.toNegated(); + export const hasSingleSelection = hasMultipleSelections.toNegated(); export const tabMovesFocus = new RawContextKey('editorTabMovesFocus', false); - export const tabDoesNotMoveFocus: ContextKeyExpr = tabMovesFocus.toNegated(); + export const tabDoesNotMoveFocus = tabMovesFocus.toNegated(); export const isInEmbeddedEditor = new RawContextKey('isInEmbeddedEditor', false); export const canUndo = new RawContextKey('canUndo', false); export const canRedo = new RawContextKey('canRedo', false); diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index b150ede69bb..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. */ @@ -335,7 +340,7 @@ export interface IIdentifiedSingleEditOperation { /** * The range to replace. This can be empty to emulate a simple insert. */ - range: Range; + range: IRange; /** * The text to replace with. This can be null to emulate a simple delete. */ @@ -358,6 +363,26 @@ export interface IIdentifiedSingleEditOperation { _isTracked?: boolean; } +export interface IValidEditOperation { + /** + * An identifier associated with this single edit operation. + * @internal + */ + identifier: ISingleEditOperationIdentifier | null; + /** + * The range to replace. This can be empty to emulate a simple insert. + */ + range: Range; + /** + * The text to replace with. This can be empty to emulate a simple delete. + */ + text: string; + /** + * @internal + */ + textChange: TextChange; +} + /** * A callback that can compute the cursor state after applying a series of edit operations. */ @@ -365,7 +390,7 @@ export interface ICursorStateComputer { /** * A callback that can compute the resulting cursors state after some edit operations have been executed. */ - (inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null; + (inverseEditOperations: IValidEditOperation[]): Selection[] | null; } export class TextModelResolvedOptions { @@ -595,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. @@ -796,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. @@ -1049,7 +1090,7 @@ export interface ITextModel { * @param cursorStateComputer A callback that can compute the resulting cursors state after the edit operations have been executed. * @return The cursor state returned by the `cursorStateComputer`. */ - pushEditOperations(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; + pushEditOperations(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; /** * Change the end of line sequence. This is the preferred way of @@ -1061,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[]): IIdentifiedSingleEditOperation[]; + 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. @@ -1071,12 +1114,22 @@ export interface ITextModel { */ setEOL(eol: EndOfLineSequence): void; + /** + * @internal + */ + _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`. * The inverse edit operations will be pushed on the redo stack. * @internal */ - undo(): Selection[] | null; + undo(): void; /** * Is there anything in the undo stack? @@ -1089,7 +1142,7 @@ export interface ITextModel { * The inverse edit operations will be pushed on the undo stack. * @internal */ - redo(): Selection[] | null; + redo(): void; /** * Is there anything in the redo stack? @@ -1206,6 +1259,20 @@ export const enum ModelConstants { FIRST_LINE_DETECTION_LENGTH_LIMIT = 1000 } +/** + * @internal + */ +export class ValidAnnotatedEditOperation implements IIdentifiedSingleEditOperation { + constructor( + public readonly identifier: ISingleEditOperationIdentifier | null, + public readonly range: Range, + public readonly text: string | null, + public readonly forceMoveMarkers: boolean, + public readonly isAutoWhitespaceEdit: boolean, + public readonly _isTracked: boolean, + ) { } +} + /** * @internal */ @@ -1234,7 +1301,7 @@ export interface ITextBuffer { getLineLastNonWhitespaceColumn(lineNumber: number): number; setEOL(newEOL: '\r\n' | '\n'): void; - applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult; + applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult; findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[]; } @@ -1244,7 +1311,7 @@ export interface ITextBuffer { export class ApplyEditsResult { constructor( - public readonly reverseEdits: IIdentifiedSingleEditOperation[], + 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 add6b310ea5..24c38f88141 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -3,61 +3,305 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +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 } 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 { TextChange, compressConsecutiveTextChanges } from 'vs/editor/common/model/textChange'; +import * as buffer from 'vs/base/common/buffer'; -interface IEditOperation { - operations: IIdentifiedSingleEditOperation[]; +function uriGetComparisonKey(resource: URI): string { + return resource.toString(); } -interface IStackElement { - readonly beforeVersionId: number; - readonly beforeCursorState: Selection[] | null; - readonly afterCursorState: Selection[] | null; - readonly afterVersionId: number; +class SingleModelEditStackData { - undo(model: TextModel): void; - redo(model: TextModel): void; -} - -class EditStackElement implements IStackElement { - public readonly beforeVersionId: number; - public readonly beforeCursorState: Selection[]; - public afterCursorState: Selection[] | null; - public afterVersionId: number; - - public editOperations: IEditOperation[]; - - constructor(beforeVersionId: number, beforeCursorState: Selection[]) { - this.beforeVersionId = beforeVersionId; - this.beforeCursorState = beforeCursorState; - this.afterCursorState = null; - this.afterVersionId = -1; - this.editOperations = []; + 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, + [] + ); } - public undo(model: TextModel): void { - // Apply all operations in reverse order - for (let i = this.editOperations.length - 1; i >= 0; i--) { - this.editOperations[i] = { - operations: model.applyEdits(this.editOperations[i].operations) - }; + 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.model = model; + this._data = SingleModelEditStackData.create(model, beforeCursorState); + } + + public setModel(model: ITextModel | URI): void { + this.model = model; + } + + public canAppend(model: ITextModel): boolean { + return (this.model === model && this._data instanceof SingleModelEditStackData); + } + + 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); } } - public redo(model: TextModel): void { - // Apply all operations - for (let i = 0; i < this.editOperations.length; i++) { - this.editOperations[i] = { - operations: model.applyEdits(this.editOperations[i].operations) - }; + public close(): void { + if (this._data instanceof SingleModelEditStackData) { + this._data = this._data.serialize(); } } + + public undo(): void { + 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 { + 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*/; + } } -function getModelEOL(model: TextModel): EndOfLineSequence { +export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement { + + public readonly type = UndoRedoElementType.Workspace; + public readonly label: string; + private _isOpen: boolean; + + private readonly _editStackElementsArr: SingleModelEditStackElement[]; + private readonly _editStackElementsMap: Map; + + public get resources(): readonly URI[] { + return this._editStackElementsArr.map(editStackElement => editStackElement.resource); + } + + constructor( + label: string, + editStackElements: SingleModelEditStackElement[] + ) { + this.label = label; + this._isOpen = true; + this._editStackElementsArr = editStackElements.slice(0); + this._editStackElementsMap = new Map(); + for (const editStackElement of this._editStackElementsArr) { + 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; + } + const key = uriGetComparisonKey(model.uri); + if (this._editStackElementsMap.has(key)) { + const editStackElement = this._editStackElementsMap.get(key)!; + return editStackElement.canAppend(model); + } + return false; + } + + 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, textChanges, afterEOL, afterVersionId, afterCursorState); + } + + public close(): void { + this._isOpen = false; + } + + public undo(): void { + this._isOpen = false; + + for (const editStackElement of this._editStackElementsArr) { + editStackElement.undo(); + } + } + + public redo(): void { + for (const editStackElement of this._editStackElementsArr) { + editStackElement.redo(); + } + } + + 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') { return EndOfLineSequence.LF; @@ -66,115 +310,66 @@ function getModelEOL(model: TextModel): EndOfLineSequence { } } -class EOLStackElement implements IStackElement { - public readonly beforeVersionId: number; - public readonly beforeCursorState: Selection[] | null; - public readonly afterCursorState: Selection[] | null; - public afterVersionId: number; - - public eol: EndOfLineSequence; - - constructor(beforeVersionId: number, setEOL: EndOfLineSequence) { - this.beforeVersionId = beforeVersionId; - this.beforeCursorState = null; - this.afterCursorState = null; - this.afterVersionId = -1; - this.eol = setEOL; +function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement { + if (!element) { + return false; } - - public undo(model: TextModel): void { - let redoEOL = getModelEOL(model); - model.setEOL(this.eol); - this.eol = redoEOL; - } - - public redo(model: TextModel): void { - let undoEOL = getModelEOL(model); - model.setEOL(this.eol); - this.eol = undoEOL; - } -} - -export interface IUndoRedoResult { - selections: Selection[] | null; - recordedVersionId: number; + return ((element instanceof SingleModelEditStackElement) || (element instanceof MultiModelEditStackElement)); } export class EditStack { - private readonly model: TextModel; - private currentOpenStackElement: IStackElement | null; - private past: IStackElement[]; - private future: IStackElement[]; + private readonly _model: TextModel; + private readonly _undoRedoService: IUndoRedoService; - constructor(model: TextModel) { - this.model = model; - this.currentOpenStackElement = null; - this.past = []; - this.future = []; + constructor(model: TextModel, undoRedoService: IUndoRedoService) { + this._model = model; + this._undoRedoService = undoRedoService; } public pushStackElement(): void { - if (this.currentOpenStackElement !== null) { - this.past.push(this.currentOpenStackElement); - this.currentOpenStackElement = null; + const lastElement = this._undoRedoService.getLastElement(this._model.uri); + if (isKnownStackElement(lastElement)) { + lastElement.close(); } } public clear(): void { - this.currentOpenStackElement = null; - this.past = []; - this.future = []; + this._undoRedoService.removeElements(this._model.uri); + } + + 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 SingleModelEditStackElement(this._model, beforeCursorState); + this._undoRedoService.pushElement(newElement); + return newElement; } public pushEOL(eol: EndOfLineSequence): void { - // No support for parallel universes :( - this.future = []; - - if (this.currentOpenStackElement) { - this.pushStackElement(); - } - - const prevEOL = getModelEOL(this.model); - let stackElement = new EOLStackElement(this.model.getAlternativeVersionId(), prevEOL); - - this.model.setEOL(eol); - - stackElement.afterVersionId = this.model.getVersionId(); - this.currentOpenStackElement = stackElement; - this.pushStackElement(); + const editStackElement = this._getOrCreateEditStackElement(null); + this._model.setEOL(eol); + editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null); } - public pushEditOperation(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { - // No support for parallel universes :( - this.future = []; - - let stackElement: EditStackElement | null = null; - - if (this.currentOpenStackElement) { - if (this.currentOpenStackElement instanceof EditStackElement) { - stackElement = this.currentOpenStackElement; - } else { - this.pushStackElement(); + public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { + const editStackElement = this._getOrCreateEditStackElement(beforeCursorState); + const inverseEditOperations = this._model.applyEdits(editOperations, true); + const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations); + 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; } - } - - if (!this.currentOpenStackElement) { - stackElement = new EditStackElement(this.model.getAlternativeVersionId(), beforeCursorState); - this.currentOpenStackElement = stackElement; - } - - const inverseEditOperation: IEditOperation = { - operations: this.model.applyEdits(editOperations) - }; - - stackElement!.editOperations.push(inverseEditOperation); - stackElement!.afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperation.operations); - stackElement!.afterVersionId = this.model.getVersionId(); - return stackElement!.afterCursorState; + 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; } - private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null { + private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null { try { return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null; } catch (e) { @@ -182,62 +377,4 @@ export class EditStack { return null; } } - - public undo(): IUndoRedoResult | null { - - this.pushStackElement(); - - if (this.past.length > 0) { - const pastStackElement = this.past.pop()!; - - try { - pastStackElement.undo(this.model); - } catch (e) { - onUnexpectedError(e); - this.clear(); - return null; - } - - this.future.push(pastStackElement); - - return { - selections: pastStackElement.beforeCursorState, - recordedVersionId: pastStackElement.beforeVersionId - }; - } - - return null; - } - - public canUndo(): boolean { - return (this.past.length > 0) || this.currentOpenStackElement !== null; - } - - public redo(): IUndoRedoResult | null { - - if (this.future.length > 0) { - const futureStackElement = this.future.pop()!; - - try { - futureStackElement.redo(this.model); - } catch (e) { - onUnexpectedError(e); - this.clear(); - return null; - } - - this.past.push(futureStackElement); - - return { - selections: futureStackElement.afterCursorState, - recordedVersionId: futureStackElement.afterVersionId - }; - } - - return null; - } - - public canRedo(): boolean { - return (this.future.length > 0); - } } 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 a987231c3ab..3dd4acda9d6 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -6,9 +6,11 @@ import * as strings from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ApplyEditsResult, EndOfLinePreference, FindMatch, IIdentifiedSingleEditOperation, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot } from 'vs/editor/common/model'; +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,12 +18,15 @@ export interface IValidatedEditOperation { range: Range; rangeOffset: number; rangeLength: number; - lines: string[] | null; + text: string; + eolCount: number; + firstLineLength: number; + lastLineLength: number; forceMoveMarkers: boolean; isAutoWhitespaceEdit: boolean; } -export interface IReverseSingleEditOperation extends IIdentifiedSingleEditOperation { +export interface IReverseSingleEditOperation extends IValidEditOperation { sortIndex: number; } @@ -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: IIdentifiedSingleEditOperation[], 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 2c1fcc8f248..81a7be60be8 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -36,6 +36,8 @@ import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 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(); @@ -187,10 +189,6 @@ export class TextModel extends Disposable implements model.ITextModel { largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations, }; - public static createFromString(text: string, options: model.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier | null = null, uri: URI | null = null): TextModel { - return new TextModel(text, options, languageIdentifier, uri); - } - public static resolveOptions(textBuffer: model.ITextBuffer, options: model.ITextModelCreationOptions): model.TextModelResolvedOptions { if (options.detectIndentation) { const guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces); @@ -253,6 +251,7 @@ export class TextModel extends Disposable implements model.ITextModel { public readonly id: string; public readonly isForSimpleWidget: boolean; private readonly _associatedResource: URI; + private readonly _undoRedoService: IUndoRedoService; private _attachedEditorCount: number; private _buffer: model.ITextBuffer; private _options: model.TextModelResolvedOptions; @@ -268,7 +267,7 @@ export class TextModel extends Disposable implements model.ITextModel { private readonly _isTooLargeForTokenization: boolean; //#region Editing - private _commandManager: EditStack; + private readonly _commandManager: EditStack; private _isUndoing: boolean; private _isRedoing: boolean; private _trimAutoWhitespaceLines: number[] | null; @@ -293,7 +292,13 @@ export class TextModel extends Disposable implements model.ITextModel { private readonly _tokenization: TextModelTokenization; //#endregion - constructor(source: string | model.ITextBufferFactory, creationOptions: model.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier | null, associatedResource: URI | null = null) { + constructor( + source: string | model.ITextBufferFactory, + creationOptions: model.ITextModelCreationOptions, + languageIdentifier: LanguageIdentifier | null, + associatedResource: URI | null = null, + undoRedoService: IUndoRedoService + ) { super(); // Generate a new unique model id @@ -305,6 +310,7 @@ export class TextModel extends Disposable implements model.ITextModel { } else { this._associatedResource = associatedResource; } + this._undoRedoService = undoRedoService; this._attachedEditorCount = 0; this._buffer = createTextBuffer(source, creationOptions.defaultEOL); @@ -347,7 +353,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._decorations = Object.create(null); this._decorationsTree = new DecorationsTrees(); - this._commandManager = new EditStack(this); + this._commandManager = new EditStack(this, undoRedoService); this._isUndoing = false; this._isRedoing = false; this._trimAutoWhitespaceLines = null; @@ -378,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 @@ -436,7 +447,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._decorationsTree = new DecorationsTrees(); // Destroy my edit history and settings - this._commandManager = new EditStack(this); + this._commandManager.clear(); this._trimAutoWhitespaceLines = null; this._emitContentChangedEvent( @@ -700,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; } @@ -1154,18 +1169,40 @@ export class TextModel extends Disposable implements model.ITextModel { } } - public pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { + private _validateEditOperation(rawOperation: model.IIdentifiedSingleEditOperation): model.ValidAnnotatedEditOperation { + if (rawOperation instanceof model.ValidAnnotatedEditOperation) { + return rawOperation; + } + return new model.ValidAnnotatedEditOperation( + rawOperation.identifier || null, + this.validateRange(rawOperation.range), + rawOperation.text, + rawOperation.forceMoveMarkers || false, + rawOperation.isAutoWhitespaceEdit || false, + rawOperation._isTracked || false + ); + } + + private _validateEditOperations(rawOperations: model.IIdentifiedSingleEditOperation[]): model.ValidAnnotatedEditOperation[] { + const result: model.ValidAnnotatedEditOperation[] = []; + for (let i = 0, len = rawOperations.length; i < len; i++) { + result[i] = this._validateEditOperation(rawOperations[i]); + } + return result; + } + + public pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); - return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer); + return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } - private _pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { + private _pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) { // Go through each saved line number and insert a trim whitespace edit // if it is safe to do so (no conflicts with other edits). @@ -1180,22 +1217,24 @@ export class TextModel extends Disposable implements model.ITextModel { // Sometimes, auto-formatters change ranges automatically which can cause undesired auto whitespace trimming near the cursor // We'll use the following heuristic: if the edits occur near the cursor, then it's ok to trim auto whitespace let editsAreNearCursors = true; - for (let i = 0, len = beforeCursorState.length; i < len; i++) { - let sel = beforeCursorState[i]; - let foundEditNearSel = false; - for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { - let editRange = incomingEdits[j].range; - let selIsAbove = editRange.startLineNumber > sel.endLineNumber; - let selIsBelow = sel.startLineNumber > editRange.endLineNumber; - if (!selIsAbove && !selIsBelow) { - foundEditNearSel = true; + if (beforeCursorState) { + for (let i = 0, len = beforeCursorState.length; i < len; i++) { + let sel = beforeCursorState[i]; + let foundEditNearSel = false; + for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { + let editRange = incomingEdits[j].range; + let selIsAbove = editRange.startLineNumber > sel.endLineNumber; + let selIsBelow = sel.startLineNumber > editRange.endLineNumber; + if (!selIsAbove && !selIsBelow) { + foundEditNearSel = true; + break; + } + } + if (!foundEditNearSel) { + editsAreNearCursors = false; break; } } - if (!foundEditNearSel) { - editsAreNearCursors = false; - break; - } } if (editsAreNearCursors) { @@ -1238,10 +1277,8 @@ export class TextModel extends Disposable implements model.ITextModel { } if (allowTrimLine) { - editOperations.push({ - range: new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn), - text: null - }); + const trimRange = new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn); + editOperations.push(new model.ValidAnnotatedEditOperation(null, trimRange, null, false, false, false)); } } @@ -1252,24 +1289,66 @@ export class TextModel extends Disposable implements model.ITextModel { return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); } - public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IIdentifiedSingleEditOperation[] { + _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(); - return this._applyEdits(rawOperations); + this._isUndoing = isUndoing; + this._isRedoing = isRedoing; + this.applyEdits(edits, false); + this.setEOL(eol); + this._overwriteAlternativeVersionId(resultingAlternativeVersionId); + } finally { + this._isUndoing = false; + this._isRedoing = false; + this._eventEmitter.endDeferredEmit(resultingSelection); + this._onDidChangeDecorations.endDeferredEmit(); + } + } + + 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(); + const operations = this._validateEditOperations(rawOperations); + return this._doApplyEdits(operations, computeUndoEdits); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } - private _applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IIdentifiedSingleEditOperation[] { - for (let i = 0, len = rawOperations.length; i < len; i++) { - rawOperations[i].range = this.validateRange(rawOperations[i].range); - } + 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; @@ -1344,65 +1423,23 @@ export class TextModel extends Disposable implements model.ITextModel { ); } - return result.reverseEdits; + return (result.reverseEdits === null ? undefined : result.reverseEdits); } - private _undo(): Selection[] | null { - this._isUndoing = true; - let r = this._commandManager.undo(); - this._isUndoing = false; - - if (!r) { - return null; - } - - this._overwriteAlternativeVersionId(r.recordedVersionId); - - return r.selections; - } - - public undo(): Selection[] | null { - try { - this._onDidChangeDecorations.beginDeferredEmit(); - this._eventEmitter.beginDeferredEmit(); - return this._undo(); - } finally { - this._eventEmitter.endDeferredEmit(); - this._onDidChangeDecorations.endDeferredEmit(); - } + public undo(): void { + this._undoRedoService.undo(this.uri); } public canUndo(): boolean { - return this._commandManager.canUndo(); + return this._undoRedoService.canUndo(this.uri); } - private _redo(): Selection[] | null { - this._isRedoing = true; - let r = this._commandManager.redo(); - this._isRedoing = false; - - if (!r) { - return null; - } - - this._overwriteAlternativeVersionId(r.recordedVersionId); - - return r.selections; - } - - public redo(): Selection[] | null { - try { - this._onDidChangeDecorations.beginDeferredEmit(); - this._eventEmitter.beginDeferredEmit(); - return this._redo(); - } finally { - this._eventEmitter.endDeferredEmit(); - this._onDidChangeDecorations.endDeferredEmit(); - } + public redo(): void { + this._undoRedoService.redo(this.uri); } public canRedo(): boolean { - return this._commandManager.canRedo(); + return this._undoRedoService.canRedo(this.uri); } //#endregion @@ -1423,19 +1460,15 @@ export class TextModel extends Disposable implements model.ITextModel { private _changeDecorations(ownerId: number, callback: (changeAccessor: model.IModelDecorationsChangeAccessor) => T): T | null { let changeAccessor: model.IModelDecorationsChangeAccessor = { addDecoration: (range: IRange, options: model.IModelDecorationOptions): string => { - this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0]; }, changeDecoration: (id: string, newRange: IRange): void => { - this._onDidChangeDecorations.fire(); this._changeDecorationImpl(id, newRange); }, changeDecorationOptions: (id: string, options: model.IModelDecorationOptions) => { - this._onDidChangeDecorations.fire(); this._changeDecorationOptionsImpl(id, _normalizeOptions(options)); }, removeDecoration: (id: string): void => { - this._onDidChangeDecorations.fire(); this._deltaDecorationsImpl(ownerId, [id], []); }, deltaDecorations: (oldDecorations: string[], newDecorations: model.IModelDeltaDecoration[]): string[] => { @@ -1443,7 +1476,6 @@ export class TextModel extends Disposable implements model.ITextModel { // nothing to do return []; } - this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); } }; @@ -1474,7 +1506,6 @@ export class TextModel extends Disposable implements model.ITextModel { try { this._onDidChangeDecorations.beginDeferredEmit(); - this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); } finally { this._onDidChangeDecorations.endDeferredEmit(); @@ -1622,6 +1653,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._decorationsTree.delete(node); node.reset(this.getVersionId(), startOffset, endOffset, range); this._decorationsTree.insert(node); + this._onDidChangeDecorations.checkAffectedAndFire(node.options); } private _changeDecorationOptionsImpl(decorationId: string, options: ModelDecorationOptions): void { @@ -1633,6 +1665,9 @@ export class TextModel extends Disposable implements model.ITextModel { const nodeWasInOverviewRuler = (node.options.overviewRuler && node.options.overviewRuler.color ? true : false); const nodeIsInOverviewRuler = (options.overviewRuler && options.overviewRuler.color ? true : false); + this._onDidChangeDecorations.checkAffectedAndFire(node.options); + this._onDidChangeDecorations.checkAffectedAndFire(options); + if (nodeWasInOverviewRuler !== nodeIsInOverviewRuler) { // Delete + Insert due to an overview ruler status change this._decorationsTree.delete(node); @@ -1666,6 +1701,7 @@ export class TextModel extends Disposable implements model.ITextModel { // (2) remove the node from the tree (if it exists) if (node) { this._decorationsTree.delete(node); + this._onDidChangeDecorations.checkAffectedAndFire(node.options); } } @@ -1688,6 +1724,7 @@ export class TextModel extends Disposable implements model.ITextModel { node.ownerId = ownerId; node.reset(versionId, startOffset, endOffset, range); node.setOptions(options); + this._onDidChangeDecorations.checkAffectedAndFire(options); this._decorationsTree.insert(node); @@ -1713,7 +1750,7 @@ export class TextModel extends Disposable implements model.ITextModel { throw new Error('Illegal value for lineNumber'); } - this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), tokens); + this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), tokens, false); } public setTokens(tokens: MultilineTokens[]): void { @@ -1725,24 +1762,61 @@ export class TextModel extends Disposable implements model.ITextModel { for (let i = 0, len = tokens.length; i < len; i++) { const element = tokens[i]; - ranges.push({ fromLineNumber: element.startLineNumber, toLineNumber: element.startLineNumber + element.tokens.length - 1 }); + let minChangedLineNumber = 0; + let maxChangedLineNumber = 0; + let hasChange = false; for (let j = 0, lenJ = element.tokens.length; j < lenJ; j++) { - this.setLineTokens(element.startLineNumber + j, element.tokens[j]); + const lineNumber = element.startLineNumber + j; + if (hasChange) { + this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), element.tokens[j], false); + maxChangedLineNumber = lineNumber; + } else { + const lineHasChange = this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), element.tokens[j], true); + if (lineHasChange) { + hasChange = true; + minChangedLineNumber = lineNumber; + maxChangedLineNumber = lineNumber; + } + } + } + if (hasChange) { + ranges.push({ fromLineNumber: minChangedLineNumber, toLineNumber: maxChangedLineNumber }); } } + if (ranges.length > 0) { + this._emitModelTokensChangedEvent({ + tokenizationSupportChanged: false, + semanticTokensApplied: false, + ranges: ranges + }); + } + } + + public setSemanticTokens(tokens: MultilineTokens2[] | null, isComplete: boolean): void { + this._tokens2.set(tokens, isComplete); + this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, - ranges: ranges + semanticTokensApplied: tokens !== null, + ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } - public setSemanticTokens(tokens: MultilineTokens2[] | null): void { - this._tokens2.set(tokens); + 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, - ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] + semanticTokensApplied: true, + ranges: [{ fromLineNumber: changedRange.startLineNumber, toLineNumber: changedRange.endLineNumber }] }); } @@ -1756,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() @@ -1768,6 +1843,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: false, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } @@ -3031,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; @@ -3050,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; @@ -3083,11 +3161,15 @@ export class DidChangeDecorationsEmitter extends Disposable { private _deferredCnt: number; private _shouldFire: boolean; + private _affectsMinimap: boolean; + private _affectsOverviewRuler: boolean; constructor() { super(); this._deferredCnt = 0; this._shouldFire = false; + this._affectsMinimap = false; + this._affectsOverviewRuler = false; } public beginDeferredEmit(): void { @@ -3098,13 +3180,31 @@ export class DidChangeDecorationsEmitter extends Disposable { this._deferredCnt--; if (this._deferredCnt === 0) { if (this._shouldFire) { + const event: IModelDecorationsChangedEvent = { + affectsMinimap: this._affectsMinimap, + affectsOverviewRuler: this._affectsOverviewRuler, + }; this._shouldFire = false; - this._actual.fire({}); + this._affectsMinimap = false; + this._affectsOverviewRuler = false; + this._actual.fire(event); } } } + public checkAffectedAndFire(options: ModelDecorationOptions): void { + if (!this._affectsMinimap) { + this._affectsMinimap = options.minimap && options.minimap.position ? true : false; + } + if (!this._affectsOverviewRuler) { + this._affectsOverviewRuler = options.overviewRuler && options.overviewRuler.color ? true : false; + } + this._shouldFire = true; + } + public fire(): void { + this._affectsMinimap = true; + this._affectsOverviewRuler = true; this._shouldFire = true; } } @@ -3134,10 +3234,11 @@ export class DidChangeContentEmitter extends Disposable { this._deferredCnt++; } - public endDeferredEmit(): void { + public endDeferredEmit(resultingSelection: Selection[] | null = null): void { this._deferredCnt--; if (this._deferredCnt === 0) { if (this._deferredEvent !== null) { + this._deferredEvent.rawContentChangedEvent.resultingSelection = resultingSelection; const e = this._deferredEvent; this._deferredEvent = null; this._fastEmitter.fire(e); diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 11fc0e4165f..6511d02173f 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IRange } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; /** * An event describing that the current mode associated with a model has changed. @@ -76,6 +77,8 @@ export interface IModelContentChangedEvent { * An event describing that model decorations have changed. */ export interface IModelDecorationsChangedEvent { + readonly affectsMinimap: boolean; + readonly affectsOverviewRuler: boolean; } /** @@ -84,6 +87,7 @@ export interface IModelDecorationsChangedEvent { */ export interface IModelTokensChangedEvent { readonly tokenizationSupportChanged: boolean; + readonly semanticTokensApplied: boolean; readonly ranges: { /** * The start of the range (inclusive) @@ -223,11 +227,14 @@ export class ModelRawContentChangedEvent { */ public readonly isRedoing: boolean; + public resultingSelection: Selection[] | null; + constructor(changes: ModelRawChange[], versionId: number, isUndoing: boolean, isRedoing: boolean) { this.changes = changes; this.versionId = versionId; this.isUndoing = isUndoing; this.isRedoing = isRedoing; + this.resultingSelection = null; } public containsEvent(type: RawContentChangedType): boolean { 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 e396aa054a2..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++; } @@ -964,10 +1180,35 @@ export class TokensStore { this._len += insertCount; } - public setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null): void { + public setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null, checkEquality: boolean): boolean { const tokens = TokensStore._massageTokens(topLevelLanguageId, lineTextLength, _tokens); this._ensureLine(lineIndex); + const oldTokens = this._lineTokens[lineIndex]; this._lineTokens[lineIndex] = tokens; + + if (checkEquality) { + return !TokensStore._equals(oldTokens, tokens); + } + return false; + } + + private static _equals(_a: Uint32Array | ArrayBuffer | null, _b: Uint32Array | ArrayBuffer | null) { + if (!_a || !_b) { + return !_a && !_b; + } + + const a = toUint32Array(_a); + const b = toUint32Array(_b); + + if (a.length !== b.length) { + return false; + } + for (let i = 0, len = a.length; i < len; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; } //#region Editing diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 0340900a5c4..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`. @@ -494,7 +499,7 @@ export interface CompletionItem { preselect?: boolean; /** * A string or snippet that should be inserted in a document when selecting - * this completion. When `falsy` the [label](#CompletionItem.label) + * this completion. * is used. */ insertText: string; @@ -635,6 +640,9 @@ export interface CodeActionList extends IDisposable { * @internal */ export interface CodeActionProvider { + + displayName?: string + /** * Provide commands for the given document and range. */ @@ -786,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. @@ -1243,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; } @@ -1324,7 +1346,7 @@ export interface WorkspaceEditMetadata { needsConfirmation: boolean; label: string; description?: string; - iconPath?: { id: string } | { light: URI, dark: URI }; + iconPath?: { id: string } | URI | { light: URI, dark: URI }; } export interface WorkspaceFileEditOptions { @@ -1370,10 +1392,19 @@ export interface RenameProvider { */ export interface AuthenticationSession { id: string; - accessToken(): Promise; + getAccessToken(): Thenable; accountName: string; } +/** + * @internal + */ +export interface AuthenticationSessionsChangeEvent { + added: string[]; + removed: string[]; + changed: string[]; +} + export interface Command { id: string; title: string; @@ -1586,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; @@ -1638,6 +1670,11 @@ export const DocumentSymbolProviderRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const OnTypeRenameProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index eacb7ed17b9..daadeebb57e 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -274,6 +274,16 @@ export class LanguageConfigurationRegistryImpl { return ensureValidWordDefinition(value.wordDefinition || null); } + public getWordDefinitions(): [LanguageId, RegExp][] { + let result: [LanguageId, RegExp][] = []; + this._entries.forEach((value, language) => { + if (value) { + result.push([language, value.wordDefinition]); + } + }); + return result; + } + public getFoldingRules(languageId: LanguageId): FoldingRules { let value = this._getRichEditSupport(languageId); if (!value) { 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/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index 78820460000..f79e864845d 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IMarkerService, IMarker, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration, MinimapPosition, IModelDecorationMinimapOptions } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; @@ -37,6 +37,10 @@ class MarkerDecorations extends Disposable { })); } + register(t: T): T { + return super._register(t); + } + public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): void { const oldIds = keys(this._markersData); this._markersData.clear(); @@ -110,6 +114,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor private _onModelAdded(model: ITextModel): void { const markerDecorations = new MarkerDecorations(model); this._markerDecorations.set(MODEL_ID(model.uri), markerDecorations); + markerDecorations.register(model.onDidChangeContent(() => this._updateDecorations(markerDecorations))); this._updateDecorations(markerDecorations); } 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 bc8824d7f89..42d2754445e 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { 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'; @@ -14,17 +15,25 @@ import { Range } from 'vs/editor/common/core/range'; 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 { 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; @@ -34,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; @@ -97,12 +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; +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; @@ -113,37 +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 + @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._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; } @@ -154,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; } @@ -203,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; } @@ -215,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]; @@ -262,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); + 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]) { @@ -305,7 +382,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { model.pushEditOperations( [], ModelServiceImpl._computeEdits(model, textBuffer), - (inverseEditOperations: IIdentifiedSingleEditOperation[]) => [] + () => [] ); model.pushStackElement(); } @@ -345,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); @@ -379,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; } @@ -388,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); } @@ -408,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(); @@ -445,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); }; @@ -470,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); } })); @@ -481,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, @@ -717,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(); @@ -727,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; } @@ -772,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) => { @@ -781,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); @@ -795,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(); } } }); @@ -821,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! @@ -834,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) { @@ -892,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) { @@ -979,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 90a9fe1b996..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 { @@ -178,105 +180,109 @@ export enum EditorOption { autoSurround = 10, codeLens = 11, colorDecorators = 12, - comments = 13, - contextmenu = 14, - copyWithSyntaxHighlighting = 15, - cursorBlinking = 16, - cursorSmoothCaretAnimation = 17, - cursorStyle = 18, - cursorSurroundingLines = 19, - cursorSurroundingLinesStyle = 20, - cursorWidth = 21, - disableLayerHinting = 22, - disableMonospaceOptimizations = 23, - dragAndDrop = 24, - emptySelectionClipboard = 25, - extraEditorClassName = 26, - fastScrollSensitivity = 27, - find = 28, - fixedOverflowWidgets = 29, - folding = 30, - foldingStrategy = 31, - foldingHighlight = 32, - fontFamily = 33, - fontInfo = 34, - fontLigatures = 35, - fontSize = 36, - fontWeight = 37, - formatOnPaste = 38, - formatOnType = 39, - glyphMargin = 40, - gotoLocation = 41, - hideCursorInOverviewRuler = 42, - highlightActiveIndentGuide = 43, - hover = 44, - inDiffEditor = 45, - letterSpacing = 46, - lightbulb = 47, - lineDecorationsWidth = 48, - lineHeight = 49, - lineNumbers = 50, - lineNumbersMinChars = 51, - links = 52, - matchBrackets = 53, - minimap = 54, - mouseStyle = 55, - mouseWheelScrollSensitivity = 56, - mouseWheelZoom = 57, - multiCursorMergeOverlapping = 58, - multiCursorModifier = 59, - multiCursorPaste = 60, - occurrencesHighlight = 61, - overviewRulerBorder = 62, - overviewRulerLanes = 63, - padding = 64, - parameterHints = 65, - peekWidgetDefaultFocus = 66, - definitionLinkOpensInPeek = 67, - quickSuggestions = 68, - quickSuggestionsDelay = 69, - readOnly = 70, - renderControlCharacters = 71, - renderIndentGuides = 72, - renderFinalNewline = 73, - renderLineHighlight = 74, - renderValidationDecorations = 75, - renderWhitespace = 76, - revealHorizontalRightPadding = 77, - roundedSelection = 78, - rulers = 79, - scrollbar = 80, - scrollBeyondLastColumn = 81, - scrollBeyondLastLine = 82, - scrollPredominantAxis = 83, - selectionClipboard = 84, - selectionHighlight = 85, - selectOnLineNumbers = 86, - showFoldingControls = 87, - showUnused = 88, - snippetSuggestions = 89, - smoothScrolling = 90, - stopRenderingLineAfter = 91, - suggest = 92, - suggestFontSize = 93, - suggestLineHeight = 94, - suggestOnTriggerCharacters = 95, - suggestSelection = 96, - tabCompletion = 97, - useTabStops = 98, - wordSeparators = 99, - wordWrap = 100, - wordWrapBreakAfterCharacters = 101, - wordWrapBreakBeforeCharacters = 102, - wordWrapColumn = 103, - wordWrapMinified = 104, - wrappingIndent = 105, - wrappingStrategy = 106, - editorClassName = 107, - pixelRatio = 108, - tabFocusMode = 109, - layoutInfo = 110, - wrappingInfo = 111 + columnSelection = 13, + comments = 14, + contextmenu = 15, + copyWithSyntaxHighlighting = 16, + cursorBlinking = 17, + cursorSmoothCaretAnimation = 18, + cursorStyle = 19, + cursorSurroundingLines = 20, + cursorSurroundingLinesStyle = 21, + cursorWidth = 22, + disableLayerHinting = 23, + disableMonospaceOptimizations = 24, + dragAndDrop = 25, + emptySelectionClipboard = 26, + extraEditorClassName = 27, + fastScrollSensitivity = 28, + find = 29, + fixedOverflowWidgets = 30, + folding = 31, + foldingStrategy = 32, + foldingHighlight = 33, + unfoldOnClickAfterEndOfLine = 34, + fontFamily = 35, + fontInfo = 36, + fontLigatures = 37, + fontSize = 38, + fontWeight = 39, + formatOnPaste = 40, + formatOnType = 41, + glyphMargin = 42, + gotoLocation = 43, + hideCursorInOverviewRuler = 44, + highlightActiveIndentGuide = 45, + hover = 46, + inDiffEditor = 47, + letterSpacing = 48, + lightbulb = 49, + lineDecorationsWidth = 50, + lineHeight = 51, + lineNumbers = 52, + lineNumbersMinChars = 53, + links = 54, + matchBrackets = 55, + minimap = 56, + mouseStyle = 57, + mouseWheelScrollSensitivity = 58, + mouseWheelZoom = 59, + multiCursorMergeOverlapping = 60, + multiCursorModifier = 61, + multiCursorPaste = 62, + occurrencesHighlight = 63, + overviewRulerBorder = 64, + overviewRulerLanes = 65, + padding = 66, + parameterHints = 67, + peekWidgetDefaultFocus = 68, + definitionLinkOpensInPeek = 69, + quickSuggestions = 70, + quickSuggestionsDelay = 71, + readOnly = 72, + 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/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index 83cb17204f6..d8645e68049 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -30,7 +30,7 @@ export const editorActiveLineNumber = registerColor('editorLineNumber.activeFore export const editorRuler = registerColor('editorRuler.foreground', { dark: '#5A5A5A', light: Color.lightgrey, hc: Color.white }, nls.localize('editorRuler', 'Color of the editor rulers.')); -export const editorCodeLensForeground = registerColor('editorCodeLens.foreground', { dark: '#999999', light: '#999999', hc: '#999999' }, nls.localize('editorCodeLensForeground', 'Foreground color of editor code lenses')); +export const editorCodeLensForeground = registerColor('editorCodeLens.foreground', { dark: '#999999', light: '#999999', hc: '#999999' }, nls.localize('editorCodeLensForeground', 'Foreground color of editor CodeLens')); export const editorBracketMatchBackground = registerColor('editorBracketMatch.background', { dark: '#0064001a', light: '#0064001a', hc: '#0064001a' }, nls.localize('editorBracketMatchBackground', 'Background color behind matching brackets')); export const editorBracketMatchBorder = registerColor('editorBracketMatch.border', { dark: '#888', light: '#B9B9B9', hc: contrastBorder }, nls.localize('editorBracketMatchBorder', 'Color for matching brackets boxes')); 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 1d9290b5e76..19c81ba6dce 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -10,6 +10,7 @@ import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; +import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents'; export const enum ViewEventType { ViewConfigurationChanged = 1, @@ -82,8 +83,17 @@ export class ViewDecorationsChangedEvent { public readonly type = ViewEventType.ViewDecorationsChanged; - constructor() { - // Nothing to do + readonly affectsMinimap: boolean; + readonly affectsOverviewRuler: boolean; + + constructor(source: IModelDecorationsChangedEvent | null) { + if (source) { + this.affectsMinimap = source.affectsMinimap; + this.affectsOverviewRuler = source.affectsOverviewRuler; + } else { + this.affectsMinimap = true; + this.affectsOverviewRuler = true; + } } } @@ -185,6 +195,7 @@ export const enum VerticalRevealType { Top = 3, Bottom = 4, NearTop = 5, + NearTopIfOutsideViewport = 6, } export class ViewRevealRangeRequestEvent { @@ -194,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; /** @@ -210,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/lineDecorations.ts b/src/vs/editor/common/viewLayout/lineDecorations.ts index 367abd68f82..74428ae28d7 100644 --- a/src/vs/editor/common/viewLayout/lineDecorations.ts +++ b/src/vs/editor/common/viewLayout/lineDecorations.ts @@ -6,6 +6,7 @@ import * as strings from 'vs/base/common/strings'; import { Constants } from 'vs/base/common/uint'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; +import { LinePartMetadata } from 'vs/editor/common/viewLayout/viewLineRenderer'; export class LineDecoration { _lineDecorationBrand: void; @@ -28,8 +29,8 @@ export class LineDecoration { } public static equalsArr(a: LineDecoration[], b: LineDecoration[]): boolean { - let aLen = a.length; - let bLen = b.length; + const aLen = a.length; + const bLen = b.length; if (aLen !== bLen) { return false; } @@ -49,8 +50,8 @@ export class LineDecoration { let result: LineDecoration[] = [], resultLen = 0; for (let i = 0, len = lineDecorations.length; i < len; i++) { - let d = lineDecorations[i]; - let range = d.range; + const d = lineDecorations[i]; + const range = d.range; if (range.endLineNumber < lineNumber || range.startLineNumber > lineNumber) { // Ignore decorations that sit outside this line @@ -62,8 +63,8 @@ export class LineDecoration { continue; } - let startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn); - let endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn); + const startColumn = (range.startLineNumber === lineNumber ? range.startColumn : minLineColumn); + const endColumn = (range.endLineNumber === lineNumber ? range.endColumn : maxLineColumn); result[resultLen++] = new LineDecoration(startColumn, endColumn, d.inlineClassName, d.type); } @@ -71,16 +72,25 @@ export class LineDecoration { return result; } + private static _typeCompare(a: InlineDecorationType, b: InlineDecorationType): number { + const ORDER = [2, 0, 1, 3]; + return ORDER[a] - ORDER[b]; + } + public static compare(a: LineDecoration, b: LineDecoration): number { if (a.startColumn === b.startColumn) { if (a.endColumn === b.endColumn) { - if (a.className < b.className) { - return -1; + const typeCmp = LineDecoration._typeCompare(a.type, b.type); + if (typeCmp === 0) { + if (a.className < b.className) { + return -1; + } + if (a.className > b.className) { + return 1; + } + return 0; } - if (a.className > b.className) { - return 1; - } - return 0; + return typeCmp; } return a.endColumn - b.endColumn; } @@ -92,11 +102,13 @@ export class DecorationSegment { startOffset: number; endOffset: number; className: string; + metadata: number; - constructor(startOffset: number, endOffset: number, className: string) { + constructor(startOffset: number, endOffset: number, className: string, metadata: number) { this.startOffset = startOffset; this.endOffset = endOffset; this.className = className; + this.metadata = metadata; } } @@ -104,13 +116,23 @@ class Stack { public count: number; private readonly stopOffsets: number[]; private readonly classNames: string[]; + private readonly metadata: number[]; constructor() { this.stopOffsets = []; this.classNames = []; + this.metadata = []; this.count = 0; } + private static _metadata(metadata: number[]): number { + let result = 0; + for (let i = 0, len = metadata.length; i < len; i++) { + result |= metadata[i]; + } + return result; + } + public consumeLowerThan(maxStopOffset: number, nextStartOffset: number, result: DecorationSegment[]): number { while (this.count > 0 && this.stopOffsets[0] < maxStopOffset) { @@ -122,34 +144,37 @@ class Stack { } // Basically we are consuming the first i + 1 elements of the stack - result.push(new DecorationSegment(nextStartOffset, this.stopOffsets[i], this.classNames.join(' '))); + result.push(new DecorationSegment(nextStartOffset, this.stopOffsets[i], this.classNames.join(' '), Stack._metadata(this.metadata))); nextStartOffset = this.stopOffsets[i] + 1; // Consume them this.stopOffsets.splice(0, i + 1); this.classNames.splice(0, i + 1); + this.metadata.splice(0, i + 1); this.count -= (i + 1); } if (this.count > 0 && nextStartOffset < maxStopOffset) { - result.push(new DecorationSegment(nextStartOffset, maxStopOffset - 1, this.classNames.join(' '))); + result.push(new DecorationSegment(nextStartOffset, maxStopOffset - 1, this.classNames.join(' '), Stack._metadata(this.metadata))); nextStartOffset = maxStopOffset; } return nextStartOffset; } - public insert(stopOffset: number, className: string): void { + public insert(stopOffset: number, className: string, metadata: number): void { if (this.count === 0 || this.stopOffsets[this.count - 1] <= stopOffset) { // Insert at the end this.stopOffsets.push(stopOffset); this.classNames.push(className); + this.metadata.push(metadata); } else { // Find the insertion position for `stopOffset` for (let i = 0; i < this.count; i++) { if (this.stopOffsets[i] >= stopOffset) { this.stopOffsets.splice(i, 0, stopOffset); this.classNames.splice(i, 0, className); + this.metadata.splice(i, 0, metadata); break; } } @@ -170,14 +195,21 @@ export class LineDecorationsNormalizer { let result: DecorationSegment[] = []; - let stack = new Stack(); + const stack = new Stack(); let nextStartOffset = 0; for (let i = 0, len = lineDecorations.length; i < len; i++) { - let d = lineDecorations[i]; + const d = lineDecorations[i]; let startColumn = d.startColumn; let endColumn = d.endColumn; - let className = d.className; + const className = d.className; + const metadata = ( + d.type === InlineDecorationType.Before + ? LinePartMetadata.PSEUDO_BEFORE + : d.type === InlineDecorationType.After + ? LinePartMetadata.PSEUDO_AFTER + : 0 + ); // If the position would end up in the middle of a high-low surrogate pair, we move it to before the pair if (startColumn > 1) { @@ -194,15 +226,15 @@ export class LineDecorationsNormalizer { } } - let currentStartOffset = startColumn - 1; - let currentEndOffset = endColumn - 2; + const currentStartOffset = startColumn - 1; + const currentEndOffset = endColumn - 2; nextStartOffset = stack.consumeLowerThan(currentStartOffset, nextStartOffset, result); if (stack.count === 0) { nextStartOffset = currentStartOffset; } - stack.insert(currentEndOffset, className); + stack.insert(currentEndOffset, className, metadata); } stack.consumeLowerThan(Constants.MAX_SAFE_SMALL_INTEGER, nextStartOffset, result); diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 05e628ab095..c698400c183 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -17,6 +17,16 @@ export const enum RenderWhitespace { All = 3 } +export const enum LinePartMetadata { + IS_WHITESPACE = 1, + PSEUDO_BEFORE = 2, + PSEUDO_AFTER = 4, + + IS_WHITESPACE_MASK = 0b001, + PSEUDO_BEFORE_MASK = 0b010, + PSEUDO_AFTER_MASK = 0b100, +} + class LinePart { _linePartBrand: void; @@ -25,10 +35,16 @@ class LinePart { */ public readonly endIndex: number; public readonly type: string; + public readonly metadata: number; - constructor(endIndex: number, type: string) { + constructor(endIndex: number, type: string, metadata: number) { this.endIndex = endIndex; this.type = type; + this.metadata = metadata; + } + + public isWhitespace(): boolean { + return (this.metadata & LinePartMetadata.IS_WHITESPACE_MASK ? true : false); } } @@ -470,7 +486,7 @@ function transformAndRemoveOverflowing(tokens: IViewLineTokens, fauxIndentLength // The faux indent part of the line should have no token type if (fauxIndentLength > 0) { - result[resultLen++] = new LinePart(fauxIndentLength, ''); + result[resultLen++] = new LinePart(fauxIndentLength, '', 0); } for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) { @@ -481,10 +497,10 @@ function transformAndRemoveOverflowing(tokens: IViewLineTokens, fauxIndentLength } const type = tokens.getClassName(tokenIndex); if (endIndex >= len) { - result[resultLen++] = new LinePart(len, type); + result[resultLen++] = new LinePart(len, type, 0); break; } - result[resultLen++] = new LinePart(endIndex, type); + result[resultLen++] = new LinePart(endIndex, type, 0); } return result; @@ -513,6 +529,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: const tokenEndIndex = token.endIndex; if (lastTokenEndIndex + Constants.LongToken < tokenEndIndex) { const tokenType = token.type; + const tokenMetadata = token.metadata; let lastSpaceOffset = -1; let currTokenStart = lastTokenEndIndex; @@ -522,13 +539,13 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: } if (lastSpaceOffset !== -1 && j - currTokenStart >= Constants.LongToken) { // Split at `lastSpaceOffset` + 1 - result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType); + result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType, tokenMetadata); currTokenStart = lastSpaceOffset + 1; lastSpaceOffset = -1; } } if (currTokenStart !== tokenEndIndex) { - result[resultLen++] = new LinePart(tokenEndIndex, tokenType); + result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata); } } else { result[resultLen++] = token; @@ -544,12 +561,13 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: let diff = (tokenEndIndex - lastTokenEndIndex); if (diff > Constants.LongToken) { const tokenType = token.type; + const tokenMetadata = token.metadata; const piecesCount = Math.ceil(diff / Constants.LongToken); for (let j = 1; j < piecesCount; j++) { let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken); - result[resultLen++] = new LinePart(pieceEndIndex, tokenType); + result[resultLen++] = new LinePart(pieceEndIndex, tokenType, tokenMetadata); } - result[resultLen++] = new LinePart(tokenEndIndex, tokenType); + result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata); } else { result[resultLen++] = token; } @@ -640,17 +658,17 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len if (generateLinePartForEachWhitespace) { const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength); for (let i = lastEndIndex + 1; i <= charIndex; i++) { - result[resultLen++] = new LinePart(i, 'mtkw'); + result[resultLen++] = new LinePart(i, 'mtkw', LinePartMetadata.IS_WHITESPACE); } } else { - result[resultLen++] = new LinePart(charIndex, 'mtkw'); + result[resultLen++] = new LinePart(charIndex, 'mtkw', LinePartMetadata.IS_WHITESPACE); } tmpIndent = tmpIndent % tabSize; } } else { // was in regular token if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) { - result[resultLen++] = new LinePart(charIndex, tokenType); + result[resultLen++] = new LinePart(charIndex, tokenType, 0); tmpIndent = tmpIndent % tabSize; } } @@ -665,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; @@ -693,13 +711,13 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len if (generateLinePartForEachWhitespace) { const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength); for (let i = lastEndIndex + 1; i <= len; i++) { - result[resultLen++] = new LinePart(i, 'mtkw'); + result[resultLen++] = new LinePart(i, 'mtkw', LinePartMetadata.IS_WHITESPACE); } } else { - result[resultLen++] = new LinePart(len, 'mtkw'); + result[resultLen++] = new LinePart(len, 'mtkw', LinePartMetadata.IS_WHITESPACE); } } else { - result[resultLen++] = new LinePart(len, tokenType); + result[resultLen++] = new LinePart(len, tokenType, 0); } return result; @@ -720,42 +738,45 @@ function _applyInlineDecorations(lineContent: string, len: number, tokens: LineP const token = tokens[tokenIndex]; const tokenEndIndex = token.endIndex; const tokenType = token.type; + const tokenMetadata = token.metadata; while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) { const lineDecoration = lineDecorations[lineDecorationIndex]; if (lineDecoration.startOffset > lastResultEndIndex) { lastResultEndIndex = lineDecoration.startOffset; - result[resultLen++] = new LinePart(lastResultEndIndex, tokenType); + result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata); } if (lineDecoration.endOffset + 1 <= tokenEndIndex) { // This line decoration ends before this token ends lastResultEndIndex = lineDecoration.endOffset + 1; - result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className); + result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata); lineDecorationIndex++; } else { // This line decoration continues on to the next token lastResultEndIndex = tokenEndIndex; - result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className); + result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata); break; } } if (tokenEndIndex > lastResultEndIndex) { lastResultEndIndex = tokenEndIndex; - result[resultLen++] = new LinePart(lastResultEndIndex, tokenType); + result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata); } } const lastTokenEndIndex = tokens[tokens.length - 1].endIndex; if (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) { let classNames: string[] = []; + let metadata = 0; while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) { classNames.push(lineDecorations[lineDecorationIndex].className); + metadata |= lineDecorations[lineDecorationIndex].metadata; lineDecorationIndex++; } - result[resultLen++] = new LinePart(lastResultEndIndex, classNames.join(' ')); + result[resultLen++] = new LinePart(lastResultEndIndex, classNames.join(' '), metadata); } return result; @@ -788,10 +809,15 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render let visibleColumn = startVisibleColumn; let charOffsetInPart = 0; + let partDisplacement = 0; 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; @@ -799,8 +825,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render const part = parts[partIndex]; const partEndIndex = part.endIndex; const partType = part.type; - const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('mtkw') >= 0)); + const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && part.isWhitespace()); const partRendersWhitespaceWithWidth = partRendersWhitespace && !fontIsMonospace && (partType === 'mtkw'/*only whitespace*/ || !containsForeignElements); + const partIsEmptyAndHasPseudoAfter = (charIndex === partEndIndex && part.metadata === LinePartMetadata.PSEUDO_AFTER); charOffsetInPart = 0; sb.appendASCIIString(''); } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index ed3b3382535..8556f11f3fd 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -33,6 +33,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel private readonly configuration: IConfiguration; private readonly model: ITextModel; private readonly _tokenizeViewportSoon: RunOnceScheduler; + private readonly _updateConfigurationViewLineCount: RunOnceScheduler; private hasFocus: boolean; private viewportStartLine: number; private viewportStartLineTrackedRange: string | null; @@ -56,6 +57,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.configuration = configuration; this.model = model; this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50)); + this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); this.hasFocus = false; this.viewportStartLine = -1; this.viewportStartLineTrackedRange = null; @@ -130,6 +132,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this._endEmit(); } })); + + this._updateConfigurationViewLineCountNow(); } public dispose(): void { @@ -142,6 +146,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); } + private _updateConfigurationViewLineCountNow(): void { + this.configuration.setViewLineCount(this.lines.getViewLineCount()); + } + public tokenizeViewport(): void { const linesViewportData = this.viewLayout.getLinesViewportData(); const startPosition = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(linesViewportData.startLineNumber, 1)); @@ -172,7 +180,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (this.lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent)) { eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); @@ -180,12 +188,14 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel // Never change the scroll position from 0 to something else... restorePreviousViewportStart = true; } + + this._updateConfigurationViewLineCount.schedule(); } if (e.hasChanged(EditorOption.readOnly)) { // Must read again all decorations due to readOnly filtering this.decorations.reset(); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); } eventsCollector.emit(new viewEvents.ViewConfigurationChangedEvent(e)); @@ -291,7 +301,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) { eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); this.decorations.onLineMappingChanged(); } } finally { @@ -301,6 +311,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel // Update the configuration and reset the centered view line this.viewportStartLine = -1; this.configuration.setMaxLineNumber(this.model.getLineCount()); + this._updateConfigurationViewLineCountNow(); // Recover viewport if (!this.hasFocus && this.model.getAttachedEditorCount() >= 2 && this.viewportStartLineTrackedRange) { @@ -354,10 +365,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const eventsCollector = this._beginEmit(); eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); } finally { this._endEmit(); } + this._updateConfigurationViewLineCount.schedule(); } })); @@ -365,7 +377,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.decorations.onModelDecorationsChanged(); try { const eventsCollector = this._beginEmit(); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(e)); } finally { this._endEmit(); } @@ -379,7 +391,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (lineMappingChanged) { eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); - eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); + eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null)); this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); this.viewLayout.onHeightMaybeChanged(); @@ -387,10 +399,29 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel } finally { this._endEmit(); } + 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 87886115932..ae17d2f9de1 100644 --- a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts +++ b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/bracketMatching'; @@ -31,10 +31,10 @@ suite('bracket matching', () => { test('issue #183: jump to matching bracket position', () => { let mode = new BracketMode(); - let model = TextModel.createFromString('var x = (3 + (5-7)) + ((5+3)+5);', undefined, mode.getLanguageIdentifier()); + 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)); @@ -63,10 +63,10 @@ suite('bracket matching', () => { test('Jump to next bracket', () => { let mode = new BracketMode(); - let model = TextModel.createFromString('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); + 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)); @@ -100,10 +100,10 @@ suite('bracket matching', () => { test('Select to next bracket', () => { let mode = new BracketMode(); - let model = TextModel.createFromString('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); + 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 @@ -152,10 +152,10 @@ suite('bracket matching', () => { '};', ].join('\n'); const mode = new BracketMode(); - const model = TextModel.createFromString(text, undefined, mode.getLanguageIdentifier()); + 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(); @@ -177,10 +177,10 @@ suite('bracket matching', () => { '};', ].join('\n'); const mode = new BracketMode(); - const model = TextModel.createFromString(text, undefined, mode.getLanguageIdentifier()); + 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); @@ -195,10 +195,10 @@ suite('bracket matching', () => { test('issue #45369: Select to Bracket with multicursor', () => { let mode = new BracketMode(); - let model = TextModel.createFromString('{ } { } { }', undefined, mode.getLanguageIdentifier()); + 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 cb106b53b82..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 @@ -161,6 +161,7 @@ class ExecCommandPasteAction extends ExecCommandAction { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_V, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, + linux: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, weight: KeybindingWeight.EditorContrib }; // Do not bind paste keybindings in the browser, diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 82ab87cacec..b9e30b80525 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -16,6 +16,7 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types'; +import { IProgress, Progress } from 'vs/platform/progress/common/progress'; export const codeActionCommandId = 'editor.action.codeAction'; export const refactorCommandId = 'editor.action.refactor'; @@ -34,6 +35,12 @@ export interface CodeActionSet extends IDisposable { class ManagedCodeActionSet extends Disposable implements CodeActionSet { private static codeActionsComparator(a: modes.CodeAction, b: modes.CodeAction): number { + if (a.isPreferred && !b.isPreferred) { + return -1; + } else if (!a.isPreferred && b.isPreferred) { + return 1; + } + if (isNonEmptyArray(a.diagnostics)) { if (isNonEmptyArray(b.diagnostics)) { return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message); @@ -70,7 +77,8 @@ export function getCodeActions( model: ITextModel, rangeOrSelection: Range | Selection, trigger: CodeActionTrigger, - token: CancellationToken + progress: IProgress, + token: CancellationToken, ): Promise { const filter = trigger.filter || {}; @@ -85,6 +93,7 @@ export function getCodeActions( const disposables = new DisposableStore(); const promises = providers.map(async provider => { try { + progress.report(provider); const providedCodeActions = await provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token); if (providedCodeActions) { disposables.add(providedCodeActions); @@ -210,6 +219,7 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, model, validatedRangeOrSelection, { type: modes.CodeActionTriggerType.Manual, filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, + Progress.None, CancellationToken.None); setTimeout(() => codeActionSet.dispose(), 100); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 185ebd86867..13b92c1289d 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -164,7 +164,7 @@ export async function applyCodeAction( }); if (action.edit) { - await bulkEditService.apply(action.edit, { editor }); + await bulkEditService.apply(action.edit, { editor, label: action.title }); } if (action.command) { diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index e8645271f35..fc35f1398ed 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -14,7 +14,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { CodeActionProviderRegistry, CodeActionTriggerType } from 'vs/editor/common/modes'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService, Progress } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; import { CodeActionTrigger } from './types'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -213,7 +213,7 @@ export class CodeActionModel extends Disposable { return; } - const actions = createCancelablePromise(token => getCodeActions(model, trigger.selection, trigger.trigger, token)); + const actions = createCancelablePromise(token => getCodeActions(model, trigger.selection, trigger.trigger, Progress.None, token)); if (this._progressService && trigger.trigger.type === CodeActionTriggerType.Manual) { this._progressService.showWhile(actions, 250); } 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/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 0af6b11bb24..85510ab2a01 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -12,6 +12,8 @@ import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; function staticCodeActionProvider(...actions: modes.CodeAction[]): modes.CodeActionProvider { return new class implements modes.CodeActionProvider { @@ -92,7 +94,7 @@ suite('CodeAction', () => { setup(function () { disposables.clear(); - model = TextModel.createFromString('test1\ntest2\ntest3', undefined, langId, uri); + model = createTextModel('test1\ntest2\ntest3', undefined, langId, uri); disposables.add(model); }); @@ -125,7 +127,7 @@ suite('CodeAction', () => { testData.tsLint.abc ]; - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 6); assert.deepEqual(actions, expected); }); @@ -140,20 +142,20 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 2); assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[1].title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b.c') } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 0); } }); @@ -172,7 +174,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); }); @@ -186,13 +188,13 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); } @@ -214,7 +216,7 @@ suite('CodeAction', () => { excludes: [CodeActionKind.Source], includeSourceActions: true, } - }, CancellationToken.None); + }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } @@ -250,7 +252,7 @@ suite('CodeAction', () => { include: baseType, excludes: [subType], } - }, CancellationToken.None); + }, Progress.None, CancellationToken.None); assert.strictEqual(didInvoke, false); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); @@ -275,7 +277,7 @@ suite('CodeAction', () => { filter: { include: CodeActionKind.QuickFix } - }, CancellationToken.None); + }, Progress.None, CancellationToken.None); assert.strictEqual(actions.length, 0); assert.strictEqual(wasInvoked, false); }); diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index c605ed49ab6..5edcc7ae851 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -15,6 +15,7 @@ import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/ import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { MarkerService } from 'vs/platform/markers/common/markerService'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; const testProvider = { provideCodeActions(): modes.CodeActionList { @@ -38,7 +39,7 @@ suite('CodeActionModel', () => { setup(() => { disposables.clear(); markerService = new MarkerService(); - model = TextModel.createFromString('foobar foo bar\nfarboo far boo', undefined, languageIdentifier, uri); + model = createTextModel('foobar foo bar\nfarboo far boo', undefined, languageIdentifier, uri); editor = createTestCodeEditor({ model: model }); editor.setPosition({ lineNumber: 1, column: 1 }); }); 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..b561522a225 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 CodeLens Commands For Current Line"), + alias: 'Show CodeLens 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/comment/blockCommentCommand.ts b/src/vs/editor/contrib/comment/blockCommentCommand.ts index efa593a811d..0a6d6a59ea5 100644 --- a/src/vs/editor/contrib/comment/blockCommentCommand.ts +++ b/src/vs/editor/contrib/comment/blockCommentCommand.ts @@ -9,7 +9,7 @@ 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 { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; +import { ITextModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; export class BlockCommentCommand implements ICommand { diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index f266b70380a..bf088f0d67c 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -205,7 +205,7 @@ export class LineCommentCommand implements ICommand { for (let i = 0, len = ops.length; i < len; i++) { builder.addEditOperation(ops[i].range, ops[i].text); - if (ops[i].range.isEmpty() && ops[i].range.getStartPosition().equals(cursorPosition)) { + if (Range.isEmpty(ops[i].range) && Range.getStartPosition(ops[i].range).equals(cursorPosition)) { const lineContent = model.getLineContent(cursorPosition.lineNumber); if (lineContent.length + 1 === cursorPosition.column) { this._deltaColumn = (ops[i].text || '').length; diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index ea3cda654bc..599e804eb05 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -68,7 +68,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu } private onEditorKeyDown(e: IKeyboardEvent): void { - if (!this._editor.getOption(EditorOption.dragAndDrop)) { + if (!this._editor.getOption(EditorOption.dragAndDrop) || this._editor.getOption(EditorOption.columnSelection)) { return; } @@ -84,7 +84,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu } private onEditorKeyUp(e: IKeyboardEvent): void { - if (!this._editor.getOption(EditorOption.dragAndDrop)) { + if (!this._editor.getOption(EditorOption.dragAndDrop) || this._editor.getOption(EditorOption.columnSelection)) { return; } 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..e7d3ddb20d5 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -19,11 +19,12 @@ 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'; import { URI } from 'vs/base/common/uri'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; export type OutlineItem = OutlineGroup | OutlineElement; @@ -38,6 +39,16 @@ export class OutlineNavigationLabelProvider implements IKeyboardNavigationLabelP } } +export class OutlineAccessibilityProvider implements IListAccessibilityProvider { + + getAriaLabel(element: OutlineItem): string | null { + if (element instanceof OutlineGroup) { + return element.provider.displayName || element.id; + } else { + return element.symbol.name; + } + } +} export class OutlineIdentityProvider implements IIdentityProvider { getId(element: OutlineItem): { toString(): string; } { @@ -150,7 +161,7 @@ export class OutlineElementRenderer implements ITreeRenderer { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const symbolIconArrayColor = theme.getColor(SYMBOL_ICON_ARRAY_FOREGROUND); if (symbolIconArrayColor) { - collector.addRule(`.codicon-symbol-array { color: ${symbolIconArrayColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-array { color: ${symbolIconArrayColor}; }`); } const symbolIconBooleanColor = theme.getColor(SYMBOL_ICON_BOOLEAN_FOREGROUND); if (symbolIconBooleanColor) { - collector.addRule(`.codicon-symbol-boolean { color: ${symbolIconBooleanColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-boolean { color: ${symbolIconBooleanColor}; }`); } const symbolIconClassColor = theme.getColor(SYMBOL_ICON_CLASS_FOREGROUND); if (symbolIconClassColor) { - collector.addRule(`.codicon-symbol-class { color: ${symbolIconClassColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-class { color: ${symbolIconClassColor}; }`); } const symbolIconMethodColor = theme.getColor(SYMBOL_ICON_METHOD_FOREGROUND); if (symbolIconMethodColor) { - collector.addRule(`.codicon-symbol-method { color: ${symbolIconMethodColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-method { color: ${symbolIconMethodColor}; }`); } const symbolIconColorColor = theme.getColor(SYMBOL_ICON_COLOR_FOREGROUND); if (symbolIconColorColor) { - collector.addRule(`.codicon-symbol-color { color: ${symbolIconColorColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-color { color: ${symbolIconColorColor}; }`); } const symbolIconConstantColor = theme.getColor(SYMBOL_ICON_CONSTANT_FOREGROUND); if (symbolIconConstantColor) { - collector.addRule(`.codicon-symbol-constant { color: ${symbolIconConstantColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-constant { color: ${symbolIconConstantColor}; }`); } const symbolIconConstructorColor = theme.getColor(SYMBOL_ICON_CONSTRUCTOR_FOREGROUND); if (symbolIconConstructorColor) { - collector.addRule(`.codicon-symbol-constructor { color: ${symbolIconConstructorColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-constructor { color: ${symbolIconConstructorColor}; }`); } const symbolIconEnumeratorColor = theme.getColor(SYMBOL_ICON_ENUMERATOR_FOREGROUND); if (symbolIconEnumeratorColor) { collector.addRule(` - .codicon-symbol-value,.codicon-symbol-enum { color: ${symbolIconEnumeratorColor} !important; }`); + .monaco-workbench .codicon-symbol-value,.monaco-workbench .codicon-symbol-enum { color: ${symbolIconEnumeratorColor}; }`); } const symbolIconEnumeratorMemberColor = theme.getColor(SYMBOL_ICON_ENUMERATOR_MEMBER_FOREGROUND); if (symbolIconEnumeratorMemberColor) { - collector.addRule(`.codicon-symbol-enum-member { color: ${symbolIconEnumeratorMemberColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-enum-member { color: ${symbolIconEnumeratorMemberColor}; }`); } const symbolIconEventColor = theme.getColor(SYMBOL_ICON_EVENT_FOREGROUND); if (symbolIconEventColor) { - collector.addRule(`.codicon-symbol-event { color: ${symbolIconEventColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-event { color: ${symbolIconEventColor}; }`); } const symbolIconFieldColor = theme.getColor(SYMBOL_ICON_FIELD_FOREGROUND); if (symbolIconFieldColor) { - collector.addRule(`.codicon-symbol-field { color: ${symbolIconFieldColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-field { color: ${symbolIconFieldColor}; }`); } const symbolIconFileColor = theme.getColor(SYMBOL_ICON_FILE_FOREGROUND); if (symbolIconFileColor) { - collector.addRule(`.codicon-symbol-file { color: ${symbolIconFileColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-file { color: ${symbolIconFileColor}; }`); } const symbolIconFolderColor = theme.getColor(SYMBOL_ICON_FOLDER_FOREGROUND); if (symbolIconFolderColor) { - collector.addRule(`.codicon-symbol-folder { color: ${symbolIconFolderColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-folder { color: ${symbolIconFolderColor}; }`); } const symbolIconFunctionColor = theme.getColor(SYMBOL_ICON_FUNCTION_FOREGROUND); if (symbolIconFunctionColor) { - collector.addRule(`.codicon-symbol-function { color: ${symbolIconFunctionColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-function { color: ${symbolIconFunctionColor}; }`); } const symbolIconInterfaceColor = theme.getColor(SYMBOL_ICON_INTERFACE_FOREGROUND); if (symbolIconInterfaceColor) { - collector.addRule(`.codicon-symbol-interface { color: ${symbolIconInterfaceColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-interface { color: ${symbolIconInterfaceColor}; }`); } const symbolIconKeyColor = theme.getColor(SYMBOL_ICON_KEY_FOREGROUND); if (symbolIconKeyColor) { - collector.addRule(`.codicon-symbol-key { color: ${symbolIconKeyColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-key { color: ${symbolIconKeyColor}; }`); } const symbolIconKeywordColor = theme.getColor(SYMBOL_ICON_KEYWORD_FOREGROUND); if (symbolIconKeywordColor) { - collector.addRule(`.codicon-symbol-keyword { color: ${symbolIconKeywordColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-keyword { color: ${symbolIconKeywordColor}; }`); } const symbolIconModuleColor = theme.getColor(SYMBOL_ICON_MODULE_FOREGROUND); if (symbolIconModuleColor) { - collector.addRule(`.codicon-symbol-module { color: ${symbolIconModuleColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-module { color: ${symbolIconModuleColor}; }`); } const outlineNamespaceColor = theme.getColor(SYMBOL_ICON_NAMESPACE_FOREGROUND); if (outlineNamespaceColor) { - collector.addRule(`.codicon-symbol-namespace { color: ${outlineNamespaceColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-namespace { color: ${outlineNamespaceColor}; }`); } const symbolIconNullColor = theme.getColor(SYMBOL_ICON_NULL_FOREGROUND); if (symbolIconNullColor) { - collector.addRule(`.codicon-symbol-null { color: ${symbolIconNullColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-null { color: ${symbolIconNullColor}; }`); } const symbolIconNumberColor = theme.getColor(SYMBOL_ICON_NUMBER_FOREGROUND); if (symbolIconNumberColor) { - collector.addRule(`.codicon-symbol-number { color: ${symbolIconNumberColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-number { color: ${symbolIconNumberColor}; }`); } const symbolIconObjectColor = theme.getColor(SYMBOL_ICON_OBJECT_FOREGROUND); if (symbolIconObjectColor) { - collector.addRule(`.codicon-symbol-object { color: ${symbolIconObjectColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-object { color: ${symbolIconObjectColor}; }`); } const symbolIconOperatorColor = theme.getColor(SYMBOL_ICON_OPERATOR_FOREGROUND); if (symbolIconOperatorColor) { - collector.addRule(`.codicon-symbol-operator { color: ${symbolIconOperatorColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-operator { color: ${symbolIconOperatorColor}; }`); } const symbolIconPackageColor = theme.getColor(SYMBOL_ICON_PACKAGE_FOREGROUND); if (symbolIconPackageColor) { - collector.addRule(`.codicon-symbol-package { color: ${symbolIconPackageColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-package { color: ${symbolIconPackageColor}; }`); } const symbolIconPropertyColor = theme.getColor(SYMBOL_ICON_PROPERTY_FOREGROUND); if (symbolIconPropertyColor) { - collector.addRule(`.codicon-symbol-property { color: ${symbolIconPropertyColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-property { color: ${symbolIconPropertyColor}; }`); } const symbolIconReferenceColor = theme.getColor(SYMBOL_ICON_REFERENCE_FOREGROUND); if (symbolIconReferenceColor) { - collector.addRule(`.codicon-symbol-reference { color: ${symbolIconReferenceColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-reference { color: ${symbolIconReferenceColor}; }`); } const symbolIconSnippetColor = theme.getColor(SYMBOL_ICON_SNIPPET_FOREGROUND); if (symbolIconSnippetColor) { - collector.addRule(`.codicon-symbol-snippet { color: ${symbolIconSnippetColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-snippet { color: ${symbolIconSnippetColor}; }`); } const symbolIconStringColor = theme.getColor(SYMBOL_ICON_STRING_FOREGROUND); if (symbolIconStringColor) { - collector.addRule(`.codicon-symbol-string { color: ${symbolIconStringColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-string { color: ${symbolIconStringColor}; }`); } const symbolIconStructColor = theme.getColor(SYMBOL_ICON_STRUCT_FOREGROUND); if (symbolIconStructColor) { - collector.addRule(`.codicon-symbol-struct { color: ${symbolIconStructColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-struct { color: ${symbolIconStructColor}; }`); } const symbolIconTextColor = theme.getColor(SYMBOL_ICON_TEXT_FOREGROUND); if (symbolIconTextColor) { - collector.addRule(`.codicon-symbol-text { color: ${symbolIconTextColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-text { color: ${symbolIconTextColor}; }`); } const symbolIconTypeParameterColor = theme.getColor(SYMBOL_ICON_TYPEPARAMETER_FOREGROUND); if (symbolIconTypeParameterColor) { - collector.addRule(`.codicon-symbol-type-parameter { color: ${symbolIconTypeParameterColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-type-parameter { color: ${symbolIconTypeParameterColor}; }`); } const symbolIconUnitColor = theme.getColor(SYMBOL_ICON_UNIT_FOREGROUND); if (symbolIconUnitColor) { - collector.addRule(`.codicon-symbol-unit { color: ${symbolIconUnitColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-unit { color: ${symbolIconUnitColor}; }`); } const symbolIconVariableColor = theme.getColor(SYMBOL_ICON_VARIABLE_FOREGROUND); if (symbolIconVariableColor) { - collector.addRule(`.codicon-symbol-variable { color: ${symbolIconVariableColor} !important; }`); + collector.addRule(`.monaco-workbench .codicon-symbol-variable { color: ${symbolIconVariableColor}; }`); } }); diff --git a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts index b41a7f8dd8f..64b8efa88a8 100644 --- a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts @@ -8,7 +8,7 @@ import { OutlineElement, OutlineGroup, OutlineModel } from '../outlineModel'; import { SymbolKind, DocumentSymbol, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { Range } from 'vs/editor/common/core/range'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { URI } from 'vs/base/common/uri'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -16,7 +16,7 @@ suite('OutlineModel', function () { test('OutlineModel#create, cached', async function () { - let model = TextModel.createFromString('foo', undefined, undefined, URI.file('/fome/path.foo')); + let model = createTextModel('foo', undefined, undefined, URI.file('/fome/path.foo')); let count = 0; let reg = DocumentSymbolProviderRegistry.register({ pattern: '**/path.foo' }, { provideDocumentSymbols() { @@ -42,7 +42,7 @@ suite('OutlineModel', function () { test('OutlineModel#create, cached/cancel', async function () { - let model = TextModel.createFromString('foo', undefined, undefined, URI.file('/fome/path.foo')); + let model = createTextModel('foo', undefined, undefined, URI.file('/fome/path.foo')); let isCancelled = false; let reg = DocumentSymbolProviderRegistry.register({ pattern: '**/path.foo' }, { diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index fee09b8429d..b22036d6b2f 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -27,6 +27,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IThemeService } from 'vs/platform/theme/common/themeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; const SEARCH_STRING_MAX_LENGTH = 524288; @@ -39,7 +40,7 @@ export function getSelectionSearchString(editor: ICodeEditor): string | null { // if selection spans multiple lines, default search string to empty if (selection.startLineNumber === selection.endLineNumber) { if (selection.isEmpty()) { - let wordAtPosition = editor.getModel().getWordAtPosition(selection.getStartPosition()); + const wordAtPosition = editor.getConfiguredWordAtPosition(selection.getStartPosition()); if (wordAtPosition) { return wordAtPosition.word; } @@ -66,6 +67,7 @@ export interface IFindStartOptions { shouldFocus: FindStartFocusAction; shouldAnimate: boolean; updateSearchScope: boolean; + loop: boolean; } export class CommonFindController extends Disposable implements IEditorContribution { @@ -125,7 +127,8 @@ export class CommonFindController extends Disposable implements IEditorContribut seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, - updateSearchScope: false + updateSearchScope: false, + loop: this._editor.getOption(EditorOption.find).loop }); } })); @@ -296,6 +299,8 @@ export class CommonFindController extends Disposable implements IEditorContribut } } + stateChanges.loop = opts.loop; + this._state.change(stateChanges, false); if (!this._model) { @@ -383,6 +388,7 @@ export class FindController extends CommonFindController implements IFindControl @IThemeService private readonly _themeService: IThemeService, @INotificationService private readonly _notificationService: INotificationService, @IStorageService _storageService: IStorageService, + @IStorageKeysSyncRegistryService private readonly _storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, @optional(IClipboardService) clipboardService: IClipboardService, ) { super(editor, _contextKeyService, _storageService, clipboardService); @@ -437,7 +443,7 @@ export class FindController extends CommonFindController implements IFindControl } private _createFindWidget() { - this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService)); + this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService, this._storageKeysSyncRegistryService)); this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService, this._themeService)); } } @@ -473,7 +479,8 @@ export class StartFindAction extends EditorAction { seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).globalFindClipboard, shouldFocus: FindStartFocusAction.FocusFindInput, shouldAnimate: true, - updateSearchScope: false + updateSearchScope: false, + loop: editor.getOption(EditorOption.find).loop }); } } @@ -507,7 +514,8 @@ export class StartFindWithSelectionAction extends EditorAction { seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: true, - updateSearchScope: false + updateSearchScope: false, + loop: editor.getOption(EditorOption.find).loop }); controller.setGlobalBufferTerm(controller.getState().searchString); @@ -524,7 +532,8 @@ export abstract class MatchFindAction extends EditorAction { seedSearchStringFromGlobalClipboard: true, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: true, - updateSearchScope: false + updateSearchScope: false, + loop: editor.getOption(EditorOption.find).loop }); this._run(controller); } @@ -636,7 +645,8 @@ export abstract class SelectionMatchFindAction extends EditorAction { seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: true, - updateSearchScope: false + updateSearchScope: false, + loop: editor.getOption(EditorOption.find).loop }); this._run(controller); } @@ -741,7 +751,8 @@ export class StartFindReplaceAction extends EditorAction { seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).seedSearchStringFromSelection, shouldFocus: shouldFocus, shouldAnimate: true, - updateSearchScope: false + updateSearchScope: false, + loop: editor.getOption(EditorOption.find).loop }); } } diff --git a/src/vs/editor/contrib/find/findDecorations.ts b/src/vs/editor/contrib/find/findDecorations.ts index ee93bccc3cf..a3f137d1575 100644 --- a/src/vs/editor/contrib/find/findDecorations.ts +++ b/src/vs/editor/contrib/find/findDecorations.ts @@ -86,7 +86,8 @@ export class FindDecorations implements IDisposable { return this._getDecorationIndex(candidate.id); } } - return 1; + // We don't know the current match position, so returns zero to show '?' in find widget + return 0; } public setCurrentFindMatch(nextMatch: Range | null): number { @@ -261,7 +262,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 +277,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 +291,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/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index 0653ac4f5c4..c083478dd00 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -20,12 +20,12 @@ import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; import { ReplaceAllCommand } from 'vs/editor/contrib/find/replaceAllCommand'; import { ReplacePattern, parseReplaceString } from 'vs/editor/contrib/find/replacePattern'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; export const CONTEXT_FIND_WIDGET_VISIBLE = new RawContextKey('findWidgetVisible', false); -export const CONTEXT_FIND_WIDGET_NOT_VISIBLE: ContextKeyExpr = CONTEXT_FIND_WIDGET_VISIBLE.toNegated(); +export const CONTEXT_FIND_WIDGET_NOT_VISIBLE = CONTEXT_FIND_WIDGET_VISIBLE.toNegated(); // Keep ContextKey use of 'Focussed' to not break when clauses export const CONTEXT_FIND_INPUT_FOCUSED = new RawContextKey('findInputFocussed', false); export const CONTEXT_REPLACE_INPUT_FOCUSED = new RawContextKey('replaceInputFocussed', false); @@ -251,6 +251,9 @@ export class FindModelBoundToEditorModel { } private _moveToPrevMatch(before: Position, isRecursed: boolean = false): void { + if (!this._state.canNavigateBack()) { + return; + } if (this._decorations.getCount() < MATCHES_LIMIT) { let prevMatchRange = this._decorations.matchBeforePosition(before); @@ -336,6 +339,9 @@ export class FindModelBoundToEditorModel { } private _moveToNextMatch(after: Position): void { + if (!this._state.canNavigateForward()) { + return; + } if (this._decorations.getCount() < MATCHES_LIMIT) { let nextMatchRange = this._decorations.matchAfterPosition(after); 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/findState.ts b/src/vs/editor/contrib/find/findState.ts index 22451cf9bed..dbadf2bb664 100644 --- a/src/vs/editor/contrib/find/findState.ts +++ b/src/vs/editor/contrib/find/findState.ts @@ -6,6 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; +import { MATCHES_LIMIT } from './findModel'; export interface FindReplaceStateChangedEvent { moveCursor: boolean; @@ -23,6 +24,7 @@ export interface FindReplaceStateChangedEvent { matchesPosition: boolean; matchesCount: boolean; currentMatch: boolean; + loop: boolean; } export const enum FindOptionOverride { @@ -45,6 +47,7 @@ export interface INewFindReplaceState { preserveCase?: boolean; preserveCaseOverride?: FindOptionOverride; searchScope?: Range | null; + loop?: boolean; } function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean { @@ -74,6 +77,7 @@ export class FindReplaceState extends Disposable { private _matchesPosition: number; private _matchesCount: number; private _currentMatch: Range | null; + private _loop: boolean; private readonly _onFindReplaceStateChange = this._register(new Emitter()); public get searchString(): string { return this._searchString; } @@ -114,6 +118,7 @@ export class FindReplaceState extends Disposable { this._matchesPosition = 0; this._matchesCount = 0; this._currentMatch = null; + this._loop = true; } public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void { @@ -131,7 +136,8 @@ export class FindReplaceState extends Disposable { searchScope: false, matchesPosition: false, matchesCount: false, - currentMatch: false + currentMatch: false, + loop: false }; let somethingChanged = false; @@ -181,7 +187,8 @@ export class FindReplaceState extends Disposable { searchScope: false, matchesPosition: false, matchesCount: false, - currentMatch: false + currentMatch: false, + loop: false }; let somethingChanged = false; @@ -237,7 +244,13 @@ export class FindReplaceState extends Disposable { somethingChanged = true; } } - + if (typeof newState.loop !== 'undefined') { + if (this._loop !== newState.loop) { + this._loop = newState.loop; + changeEvent.loop = true; + somethingChanged = true; + } + } // Overrides get set when they explicitly come in and get reset anytime something else changes this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet); this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet); @@ -266,4 +279,17 @@ export class FindReplaceState extends Disposable { this._onFindReplaceStateChange.fire(changeEvent); } } + + public canNavigateBack(): boolean { + return this.canNavigateInLoop() || (this.matchesPosition !== 1); + } + + public canNavigateForward(): boolean { + return this.canNavigateInLoop() || (this.matchesPosition < this.matchesCount); + } + + private canNavigateInLoop(): boolean { + return this._loop || (this.matchesCount >= MATCHES_LIMIT); + } + } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 96c7b3a0d17..613d0654c6b 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -31,11 +31,12 @@ 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'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export interface IFindController { replace(): void; @@ -152,6 +153,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas themeService: IThemeService, storageService: IStorageService, notificationService: INotificationService, + storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); this._codeEditor = codeEditor; @@ -163,6 +165,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._storageService = storageService; this._notificationService = notificationService; + storageKeysSyncRegistryService.registerStorageKey({ key: ctrlEnterReplaceAllWarningPromptedKey, version: 1 }); this._ctrlEnterReplaceAllWarningPrompted = !!storageService.getBoolean(ctrlEnterReplaceAllWarningPromptedKey, StorageScope.GLOBAL); this._isVisible = false; @@ -244,8 +247,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) { @@ -360,6 +363,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (e.updateHistory) { this._delayedUpdateHistory(); } + if (e.loop) { + this._updateButtons(); + } } private _delayedUpdateHistory() { @@ -419,8 +425,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } if (currentMatch) { const ariaLabel = nls.localize('ariaSearchNoResultWithLineNum', "{0} found for '{1}', at {2}", label, searchString, currentMatch.startLineNumber + ':' + currentMatch.startColumn); - const lineContent = this._codeEditor.getModel()?.getLineContent(currentMatch.startLineNumber); - if (lineContent) { + const model = this._codeEditor.getModel(); + if (model && (currentMatch.startLineNumber <= model.getLineCount()) && (currentMatch.startLineNumber >= 1)) { + const lineContent = model.getLineContent(currentMatch.startLineNumber); return `${lineContent}, ${ariaLabel}`; } @@ -454,8 +461,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas let findInputIsNonEmpty = (this._state.searchString.length > 0); let matchesCount = this._state.matchesCount ? true : false; - this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount); - this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount); + this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount && this._state.canNavigateBack()); + this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount && this._state.canNavigateForward()); this._replaceBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty); this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty); @@ -642,7 +649,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), @@ -923,7 +930,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..633a70af76e 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(); @@ -276,7 +276,8 @@ suite('FindController', () => { seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.FocusFindInput, shouldAnimate: false, - updateSearchScope: false + updateSearchScope: false, + loop: true }); nextMatchFindAction.run(null, editor); startFindReplaceAction.run(null, editor); @@ -294,14 +295,15 @@ 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, seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, - updateSearchScope: false + updateSearchScope: false, + loop: true }); assert.equal(findController.getState().searchScope, null); @@ -322,7 +324,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 +351,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 +378,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 +405,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 +456,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 +483,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 +508,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 +524,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, @@ -530,7 +532,8 @@ suite('FindController query options persistence', () => { seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, - updateSearchScope: true + updateSearchScope: true, + loop: true }); assert.deepEqual(findController.getState().searchScope, new Selection(1, 1, 2, 1)); @@ -545,7 +548,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, @@ -553,7 +556,8 @@ suite('FindController query options persistence', () => { seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, - updateSearchScope: true + updateSearchScope: true, + loop: true }); assert.deepEqual(findController.getState().searchScope, null); @@ -568,7 +572,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, @@ -576,7 +580,8 @@ suite('FindController query options persistence', () => { seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, - updateSearchScope: true + updateSearchScope: true, + loop: true }); assert.deepEqual(findController.getState().searchScope, new Selection(1, 2, 1, 3)); @@ -592,7 +597,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, @@ -600,7 +605,8 @@ suite('FindController query options persistence', () => { seedSearchStringFromGlobalClipboard: false, shouldFocus: FindStartFocusAction.NoFocusChange, shouldAnimate: false, - updateSearchScope: true + updateSearchScope: true, + loop: true }); assert.deepEqual(findController.getState().searchScope, new Selection(1, 6, 2, 1)); diff --git a/src/vs/editor/contrib/find/test/findModel.test.ts b/src/vs/editor/contrib/find/test/findModel.test.ts index df936e17485..d83c9ce4d34 100644 --- a/src/vs/editor/contrib/find/test/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/findModel.test.ts @@ -15,6 +15,9 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { FindModelBoundToEditorModel } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +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'; suite('FindModel', () => { @@ -44,7 +47,7 @@ suite('FindModel', () => { const factory = ptBuilder.finish(); withTestCodeEditor([], { - model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null) + model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null, new UndoRedoService(new TestDialogService(), new TestNotificationService())) }, (editor, cursor) => callback(editor as unknown as IActiveCodeEditor, cursor) ); @@ -2086,4 +2089,165 @@ suite('FindModel', () => { findModel.dispose(); findState.dispose(); }); + + findTest('issue #3516: Control behavior of "Next" operations (not looping back to beginning)', (editor, cursor) => { + let findState = new FindReplaceState(); + findState.change({ searchString: 'hello', loop: false }, false); + let findModel = new FindModelBoundToEditorModel(editor, findState); + + assert.equal(findState.matchesCount, 5); + + // Test next operations + assert.equal(findState.matchesPosition, 0); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 1); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), false); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 2); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 3); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 4); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 5); + assert.equal(findState.canNavigateForward(), false); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 5); + assert.equal(findState.canNavigateForward(), false); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 5); + assert.equal(findState.canNavigateForward(), false); + assert.equal(findState.canNavigateBack(), true); + + // Test previous operations + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 4); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 3); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 2); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 1); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), false); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 1); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), false); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 1); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), false); + + }); + + findTest('issue #3516: Control behavior of "Next" operations (looping back to beginning)', (editor, cursor) => { + let findState = new FindReplaceState(); + findState.change({ searchString: 'hello' }, false); + let findModel = new FindModelBoundToEditorModel(editor, findState); + + assert.equal(findState.matchesCount, 5); + + // Test next operations + assert.equal(findState.matchesPosition, 0); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 1); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 2); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 3); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 4); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 5); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 1); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToNextMatch(); + assert.equal(findState.matchesPosition, 2); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + // Test previous operations + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 1); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 5); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 4); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 3); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 2); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + findModel.moveToPrevMatch(); + assert.equal(findState.matchesPosition, 1); + assert.equal(findState.canNavigateForward(), true); + assert.equal(findState.canNavigateBack(), true); + + }); + }); diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 71cc7b34b0e..e80d1f68e5f 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -19,7 +19,7 @@ import { FoldingDecorationProvider } from './foldingDecorations'; import { FoldingRegions, FoldingRegion } from './foldingRanges'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; +import { IMarginData, IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { HiddenRangeModel } from 'vs/editor/contrib/folding/hiddenRangeModel'; import { IRange } from 'vs/editor/common/core/range'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; @@ -62,6 +62,7 @@ export class FoldingController extends Disposable implements IEditorContribution private readonly editor: ICodeEditor; private _isEnabled: boolean; private _useFoldingProviders: boolean; + private _unfoldOnClickAfterEndOfLine: boolean; private readonly foldingDecorationProvider: FoldingDecorationProvider; @@ -91,6 +92,7 @@ export class FoldingController extends Disposable implements IEditorContribution const options = this.editor.getOptions(); this._isEnabled = options.get(EditorOption.folding); this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation'; + this._unfoldOnClickAfterEndOfLine = options.get(EditorOption.unfoldOnClickAfterEndOfLine); this.foldingModel = null; this.hiddenRangeModel = null; @@ -112,8 +114,7 @@ export class FoldingController extends Disposable implements IEditorContribution this._register(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (e.hasChanged(EditorOption.folding)) { - const options = this.editor.getOptions(); - this._isEnabled = options.get(EditorOption.folding); + this._isEnabled = this.editor.getOptions().get(EditorOption.folding); this.foldingEnabled.set(this._isEnabled); this.onModelChanged(); } @@ -124,10 +125,12 @@ export class FoldingController extends Disposable implements IEditorContribution this.onModelContentChanged(); } if (e.hasChanged(EditorOption.foldingStrategy)) { - const options = this.editor.getOptions(); - this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation'; + this._useFoldingProviders = this.editor.getOptions().get(EditorOption.foldingStrategy) !== 'indentation'; this.onFoldingStrategyChanged(); } + if (e.hasChanged(EditorOption.unfoldOnClickAfterEndOfLine)) { + this._unfoldOnClickAfterEndOfLine = this.editor.getOptions().get(EditorOption.unfoldOnClickAfterEndOfLine); + } })); this.onModelChanged(); } @@ -364,6 +367,15 @@ export class FoldingController extends Disposable implements IEditorContribution iconClicked = true; break; + case MouseTargetType.CONTENT_EMPTY: { + if (this._unfoldOnClickAfterEndOfLine && this.hiddenRangeModel.hasRanges()) { + const data = e.target.detail as IEmptyContentData; + if (!data.isAfterLines) { + break; + } + } + return; + } case MouseTargetType.CONTENT_TEXT: { if (this.hiddenRangeModel.hasRanges()) { let model = this.editor.getModel(); @@ -885,7 +897,7 @@ for (let i = 1; i <= 7; i++) { ); } -export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hc: null }, nls.localize('foldBackgroundBackground', "Background color behind folded ranges.")); +export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hc: null }, nls.localize('foldBackgroundBackground', "Background color behind folded ranges. The color must not be opaque so as not to hide underlying decorations."), true); registerThemingParticipant((theme, collector) => { const foldBackground = theme.getColor(foldBackgroundBackground); 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 87470e12e09..a646fa78bd6 100644 --- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { FoldingModel, setCollapseStateAtLevel, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines, setCollapseStateUp } from 'vs/editor/contrib/folding/foldingModel'; -import { TextModel, ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { TrackedRangeStickiness, IModelDeltaDecoration, ITextModel, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -20,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' }); @@ -30,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[] { @@ -41,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', () => { @@ -48,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) { @@ -77,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); } @@ -92,7 +138,7 @@ suite('Folding Model', () => { /* 7*/ ' }', /* 8*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -131,7 +177,7 @@ suite('Folding Model', () => { /* 7*/ ' }', /* 8*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -177,7 +223,7 @@ suite('Folding Model', () => { /* 7*/ ' }', /* 8*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -217,7 +263,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -254,7 +300,7 @@ suite('Folding Model', () => { /* 7*/ ' }', /* 8*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -295,7 +341,7 @@ suite('Folding Model', () => { /* 11*/ ' }', /* 12*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -346,7 +392,7 @@ suite('Folding Model', () => { /* 10*/ '//#endregion', /* 11*/ '']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -392,7 +438,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -448,7 +494,7 @@ suite('Folding Model', () => { /* 15*/ ' //#endregion', /* 16*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -504,7 +550,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -556,7 +602,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -603,7 +649,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -648,7 +694,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -671,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/folding/test/foldingRanges.test.ts b/src/vs/editor/contrib/folding/test/foldingRanges.test.ts index dd572ec9850..214135a9c8c 100644 --- a/src/vs/editor/contrib/folding/test/foldingRanges.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingRanges.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration'; import { MAX_FOLDING_REGIONS } from 'vs/editor/contrib/folding/foldingRanges'; @@ -26,7 +26,7 @@ suite('FoldingRanges', () => { for (let i = 0; i < nRegions; i++) { lines.push('#endregion'); } - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); let actual = computeRanges(model, false, markers, MAX_FOLDING_REGIONS); assert.equal(actual.length, nRegions, 'len'); for (let i = 0; i < nRegions; i++) { @@ -53,7 +53,7 @@ suite('FoldingRanges', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let actual = computeRanges(textModel, false, markers); // let r0 = r(1, 2); @@ -91,7 +91,7 @@ suite('FoldingRanges', () => { for (let i = 0; i < nRegions; i++) { lines.push('#endregion'); } - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); let actual = computeRanges(model, false, markers, MAX_FOLDING_REGIONS); assert.equal(actual.length, nRegions, 'len'); for (let i = 0; i < nRegions; i++) { diff --git a/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts b/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts index 18fa5acd3fe..9db5f45e4fb 100644 --- a/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts +++ b/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { FoldingModel } from 'vs/editor/contrib/folding/foldingModel'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { TestDecorationProvider } from './foldingModel.test'; import { HiddenRangeModel } from 'vs/editor/contrib/folding/hiddenRangeModel'; @@ -38,7 +38,7 @@ suite('Hidden Range Model', () => { /* 9*/ ' }', /* 10*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); let hiddenRangeModel = new HiddenRangeModel(foldingModel); diff --git a/src/vs/editor/contrib/folding/test/indentFold.test.ts b/src/vs/editor/contrib/folding/test/indentFold.test.ts index ed51ec95bac..ccdb26a756e 100644 --- a/src/vs/editor/contrib/folding/test/indentFold.test.ts +++ b/src/vs/editor/contrib/folding/test/indentFold.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; interface IndentRange { start: number; @@ -47,7 +47,7 @@ suite('Indentation Folding', () => { let r8 = r(13, 14);//4 let r9 = r(15, 16);//0 - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); function assertLimit(maxEntries: number, expectedRanges: IndentRange[], message: string) { let indentRanges = computeRanges(model, true, undefined, maxEntries); diff --git a/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts b/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts index 07e20256667..8a4dbe7f38a 100644 --- a/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts +++ b/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration'; @@ -15,7 +15,7 @@ interface ExpectedIndentRange { } function assertRanges(lines: string[], expected: ExpectedIndentRange[], offside: boolean, markers?: FoldingMarkers): void { - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); let actual = computeRanges(model, offside, markers); let actualRanges: ExpectedIndentRange[] = []; diff --git a/src/vs/editor/contrib/folding/test/syntaxFold.test.ts b/src/vs/editor/contrib/folding/test/syntaxFold.test.ts index 480b84e5d7c..86a4280259e 100644 --- a/src/vs/editor/contrib/folding/test/syntaxFold.test.ts +++ b/src/vs/editor/contrib/folding/test/syntaxFold.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 { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { SyntaxRangeProvider } from 'vs/editor/contrib/folding/syntaxRangeProvider'; import { FoldingRangeProvider, FoldingRange, FoldingContext, ProviderResult } from 'vs/editor/common/modes'; import { ITextModel } from 'vs/editor/common/model'; @@ -69,7 +69,7 @@ suite('Syntax folding', () => { let r8 = r(14, 15); //6 let r9 = r(22, 23); //0 - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); let ranges = [r1, r2, r3, r4, r5, r6, r7, r8, r9]; let providers = [new TestFoldingRangeProvider(model, ranges)]; diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index 84e4007bce6..14dac4565c1 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -27,6 +27,8 @@ import { IDisposable } from 'vs/base/common/lifecycle'; 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 { @@ -110,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); } @@ -209,6 +211,7 @@ export async function formatDocumentWithSelectedProvider( accessor: ServicesAccessor, editorOrModel: ITextModel | IActiveCodeEditor, mode: FormattingMode, + progress: IProgress, token: CancellationToken ): Promise { @@ -217,6 +220,7 @@ export async function formatDocumentWithSelectedProvider( const provider = getRealAndSyntheticDocumentFormattersOrdered(model); const selected = await FormattingConflicts.select(provider, model, mode); if (selected) { + progress.report(selected); await instaService.invokeFunction(formatDocumentWithProvider, selected, editorOrModel, mode, token); } } diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index c169060f72a..9d1c8ec0388 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -25,6 +25,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Progress } from 'vs/platform/progress/common/progress'; class FormatOnType implements IEditorContribution { @@ -230,7 +231,7 @@ class FormatDocumentAction extends EditorAction { async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { if (editor.hasModel()) { const instaService = accessor.get(IInstantiationService); - await instaService.invokeFunction(formatDocumentWithSelectedProvider, editor, FormattingMode.Explicit, CancellationToken.None); + await instaService.invokeFunction(formatDocumentWithSelectedProvider, editor, FormattingMode.Explicit, Progress.None, CancellationToken.None); } } } diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 0de7e22fdb5..f1b1c298d1c 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -397,7 +397,7 @@ class MarkerNavigationAction extends EditorAction { return editorService.openCodeEditor({ resource: newMarker.resource, - options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, selection: newMarker } + options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.NearTop, selection: newMarker } }, editor).then(editor => { if (!editor) { return undefined; diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 68f5f944373..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'; @@ -52,11 +52,11 @@ class MessageWidget { const domNode = document.createElement('div'); domNode.className = 'descriptioncontainer'; - domNode.setAttribute('aria-live', 'assertive'); - domNode.setAttribute('role', 'alert'); this._messageBlock = document.createElement('div'); dom.addClass(this._messageBlock, 'message'); + this._messageBlock.setAttribute('aria-live', 'assertive'); + this._messageBlock.setAttribute('role', 'alert'); domNode.appendChild(this._messageBlock); this._relatedBlock = document.createElement('div'); @@ -88,7 +88,8 @@ class MessageWidget { dispose(this._disposables); } - update({ source, message, relatedInformation, code }: IMarker): void { + update(marker: IMarker): void { + const { source, message, relatedInformation, code } = marker; let sourceAndCodeLength = (source?.length || 0) + '()'.length; if (code) { if (typeof code === 'string') { @@ -106,6 +107,7 @@ class MessageWidget { } dom.clearNode(this._messageBlock); + this._messageBlock.setAttribute('aria-label', this.getAriaLabel(marker)); this._editor.applyFontInfo(this._messageBlock); let lastLineElement = this._messageBlock; for (const line of lines) { @@ -192,6 +194,32 @@ class MessageWidget { getHeightInLines(): number { return Math.min(17, this._lines); } + + private getAriaLabel(marker: IMarker): string { + let severityLabel = ''; + switch (marker.severity) { + case MarkerSeverity.Error: + severityLabel = nls.localize('Error', "Error"); + break; + case MarkerSeverity.Warning: + severityLabel = nls.localize('Warning', "Warning"); + break; + case MarkerSeverity.Info: + severityLabel = nls.localize('Info', "Info"); + break; + case MarkerSeverity.Hint: + severityLabel = nls.localize('Hint', "Hint"); + break; + } + + let ariaLabel = nls.localize('marker aria', "{0} at {1}. ", severityLabel, marker.startLineNumber + ':' + marker.startColumn); + const model = this._editor.getModel(); + if (model && (marker.startLineNumber <= model.getLineCount()) && (marker.startLineNumber >= 1)) { + const lineContent = model.getLineContent(marker.startLineNumber); + ariaLabel = `${lineContent}, ${ariaLabel}`; + } + return ariaLabel; + } } export class MarkerNavigationWidget extends PeekViewWidget { @@ -218,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) { @@ -299,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); @@ -316,7 +344,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { } this._icon.className = `codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(this._severity))}`; - this.editor.revealPositionInCenter(position, ScrollType.Smooth); + this.editor.revealPositionNearTop(position, ScrollType.Smooth); this.editor.focus(); } 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/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts index f1857f14862..86ffdbf79a1 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -167,7 +167,7 @@ abstract class SymbolNavigationAction extends EditorAction { resource: reference.uri, options: { selection: Range.collapseToStart(range), - selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport + selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport } }, editor, sideBySide); 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/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index a2ac8275f37..68c3275a7a4 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -338,9 +338,8 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri private gotoDefinition(position: Position, openToSide: boolean): Promise { this.editor.setPosition(position); - const definitionLinkOpensInPeek = this.editor.getOption(EditorOption.definitionLinkOpensInPeek); return this.editor.invokeWithinContext((accessor) => { - const canPeek = definitionLinkOpensInPeek && !this.isInPeekEditor(accessor); + const canPeek = !openToSide && this.editor.getOption(EditorOption.definitionLinkOpensInPeek) && !this.isInPeekEditor(accessor); const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined }); return action.run(accessor, this.editor); }); 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/referencesTree.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts index 1b93c77febb..187b717b46e 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts @@ -17,7 +17,7 @@ import { getBaseLabel } from 'vs/base/common/labels'; import { dirname, basename } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -213,7 +213,7 @@ export class OneReferenceRenderer implements ITreeRenderer { +export class AccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: FileReferences | OneReference): string | null { return element.ariaMessage; diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 7c279e25546..abffc47cde7 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -22,17 +22,18 @@ import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/ import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel'; import { Location } from 'vs/editor/common/modes'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree'; +import { AccessibilityProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree'; import * as nls from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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'; import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; class DecorationsManager implements IDisposable { @@ -215,12 +216,13 @@ export class ReferenceWidget extends peekView.PeekViewWidget { @ITextModelService private readonly _textModelResolverService: ITextModelService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService, - @ILabelService private readonly _uriLabel: ILabelService + @ILabelService private readonly _uriLabel: ILabelService, + @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, ) { 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(); } @@ -237,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, @@ -304,14 +306,14 @@ export class ReferenceWidget extends peekView.PeekViewWidget { }; this._preview = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._previewContainer, options, this.editor); dom.hide(this._previewContainer); - this._previewNotAvailableMessage = TextModel.createFromString(nls.localize('missingPreviewMessage', "no preview available")); + this._previewNotAvailableMessage = new TextModel(nls.localize('missingPreviewMessage', "no preview available"), TextModel.DEFAULT_CREATION_OPTIONS, null, null, this._undoRedoService); // tree this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline')); const treeOptions: IWorkbenchAsyncDataTreeOptions = { ariaLabel: nls.localize('treeAriaLabel', "References"), keyboardSupport: this._defaultTreeKeyboardSupport, - accessibilityProvider: new AriaProvider(), + accessibilityProvider: new AccessibilityProvider(), keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider), identityProvider: new IdentityProvider(), overrideStyles: { diff --git a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts index 0385d330427..d082bb98f43 100644 --- a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts +++ b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts @@ -128,7 +128,7 @@ class SymbolNavigationService implements ISymbolNavigationService { resource: reference.uri, options: { selection: Range.collapseToStart(reference.range), - selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport + selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport } }, source).finally(() => { this._ignoreEditorChange = false; diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 3e41cf7b901..fd3f3733d7a 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -40,6 +40,7 @@ import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Constants } from 'vs/base/common/uint'; import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Progress } from 'vs/platform/progress/common/progress'; const $ = dom.$; @@ -503,34 +504,33 @@ export class ModesContentHoverWidget extends ContentHoverWidget { messageElement.innerText = message; if (source || code) { - if (typeof code === 'string') { + // Code has link + if (code && typeof code !== 'string') { + const sourceAndCodeElement = $('span'); + if (source) { + const sourceElement = dom.append(sourceAndCodeElement, $('span')); + sourceElement.innerText = source; + } + this._codeLink = dom.append(sourceAndCodeElement, $('a.code-link')); + this._codeLink.setAttribute('href', code.target.toString()); + + this._codeLink.onclick = (e) => { + this._openerService.open(code.target); + e.preventDefault(); + e.stopPropagation(); + }; + + const codeElement = dom.append(this._codeLink, $('span')); + codeElement.innerText = code.value; + + const detailsElement = dom.append(markerElement, sourceAndCodeElement); + detailsElement.style.opacity = '0.6'; + detailsElement.style.paddingLeft = '6px'; + } else { const detailsElement = dom.append(markerElement, $('span')); detailsElement.style.opacity = '0.6'; detailsElement.style.paddingLeft = '6px'; detailsElement.innerText = source && code ? `${source}(${code})` : source ? source : `(${code})`; - } else { - if (code) { - const sourceAndCodeElement = $('span'); - if (source) { - const sourceElement = dom.append(sourceAndCodeElement, $('span')); - sourceElement.innerText = source; - } - this._codeLink = dom.append(sourceAndCodeElement, $('a.code-link')); - this._codeLink.setAttribute('href', code.target.toString()); - - this._codeLink.onclick = (e) => { - this._openerService.open(code.target); - e.preventDefault(); - e.stopPropagation(); - }; - - const codeElement = dom.append(this._codeLink, $('span')); - codeElement.innerText = code.value; - - const detailsElement = dom.append(markerElement, sourceAndCodeElement); - detailsElement.style.opacity = '0.6'; - detailsElement.style.paddingLeft = '6px'; - } } } @@ -580,7 +580,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 => { @@ -626,6 +625,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), markerCodeActionTrigger, + Progress.None, cancellationToken); }); } @@ -637,11 +637,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/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index c9bf8a68056..174302f90a1 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -960,7 +960,7 @@ export abstract class AbstractCaseAction extends EditorAction { let selection = selections[i]; if (selection.isEmpty()) { let cursor = selection.getStartPosition(); - let word = model.getWordAtPosition(cursor); + const word = editor.getConfiguredWordAtPosition(cursor); if (!word) { continue; diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index 64b26b23b76..6090bd19e7e 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -317,7 +317,7 @@ suite('Editor Contrib - Line Operations', () => { assert.equal(model.getLineContent(1), 'one'); assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 1)); - editor.trigger('keyboard', Handler.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Typing some text here on line one'); assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31)); }); @@ -447,7 +447,7 @@ suite('Editor Contrib - Line Operations', () => { assert.equal(model.getLineContent(1), 'hello my dear world'); assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); - editor.trigger('keyboard', Handler.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'hello my dear'); assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); }); @@ -815,13 +815,13 @@ suite('Editor Contrib - Line Operations', () => { new Selection(2, 4, 2, 4) ]); - editor.trigger('tests', Handler.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.deepEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(1, 6, 1, 6), new Selection(3, 4, 3, 4) ]); - editor.trigger('tests', Handler.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.deepEqual(editor.getSelections(), [ new Selection(1, 3, 1, 3), new Selection(2, 4, 2, 4) diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index a85e7f8c942..0ee40d5166e 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -77,8 +77,8 @@ export class LinksList extends Disposable { const newLinks = list.links.map(link => new Link(link, provider)); links = LinksList._union(links, newLinks); // register disposables - if (isDisposable(provider)) { - this._register(provider); + if (isDisposable(list)) { + this._register(list); } } this.links = links; diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index bf5399f274a..9e3c455e31a 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -25,6 +25,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import * as resources from 'vs/base/common/resources'; +import * as strings from 'vs/base/common/strings'; function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString { const executeCmd = link.url && /^command:/i.test(link.url.toString()); @@ -97,7 +101,7 @@ class LinkOccurrence { } } -class LinkDetector implements IEditorContribution { +export class LinkDetector implements IEditorContribution { public static readonly ID: string = 'editor.linkDetector'; @@ -291,7 +295,29 @@ class LinkDetector implements IEditorContribution { const { link } = occurrence; link.resolve(CancellationToken.None).then(uri => { - // open the uri + + // Support for relative file URIs of the shape file://./relativeFile.txt or file:///./relativeFile.txt + if (typeof uri === 'string' && this.editor.hasModel()) { + const modelUri = this.editor.getModel().uri; + if (modelUri.scheme === Schemas.file && strings.startsWith(uri, 'file:')) { + const parsedUri = URI.parse(uri); + if (parsedUri.scheme === Schemas.file) { + const fsPath = resources.originalFSPath(parsedUri); + + let relativePath: string | null = null; + if (strings.startsWith(fsPath, '/./')) { + relativePath = `.${fsPath.substr(1)}`; + } else if (strings.startsWith(fsPath, '//./')) { + relativePath = `.${fsPath.substr(2)}`; + } + + if (relativePath) { + uri = resources.joinPath(modelUri, relativePath); + } + } + } + } + return this.openerService.open(uri, { openToSide, fromUserGesture }); }, err => { diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index b23cc0c09f8..abebedd5080 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -286,7 +286,7 @@ export class MultiCursorSession { if (s.isEmpty()) { // selection is empty => expand to current word - const word = editor.getModel().getWordAtPosition(s.getStartPosition()); + const word = editor.getConfiguredWordAtPosition(s.getStartPosition()); if (!word) { return null; } @@ -505,7 +505,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito if (!selection.isEmpty()) { return selection; } - const word = model.getWordAtPosition(selection.getStartPosition()); + const word = this._editor.getConfiguredWordAtPosition(selection.getStartPosition()); if (!word) { return selection; } @@ -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/parameterHints/test/parameterHintsModel.test.ts b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts index 2aa301d692a..2d5e1967fe7 100644 --- a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts +++ b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import * as modes from 'vs/editor/common/modes'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -49,7 +49,7 @@ suite('ParameterHintsModel', () => { }); function createMockEditor(fileContents: string) { - const textModel = TextModel.createFromString(fileContents, undefined, undefined, mockFile); + const textModel = createTextModel(fileContents, undefined, undefined, mockFile); const editor = createTestCodeEditor({ model: textModel, serviceCollection: new ServiceCollection( diff --git a/src/vs/editor/contrib/peekView/peekView.ts b/src/vs/editor/contrib/peekView/peekView.ts index 32503e62570..83c36e037d7 100644 --- a/src/vs/editor/contrib/peekView/peekView.ts +++ b/src/vs/editor/contrib/peekView/peekView.ts @@ -17,7 +17,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IOptions, IStyles, ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; -import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -57,7 +57,7 @@ registerSingleton(IPeekViewService, class implements IPeekViewService { export namespace PeekContext { export const inPeekEditor = new RawContextKey('inReferenceSearchEditor', true); - export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated(); + export const notInPeekEditor = inPeekEditor.toNegated(); } class PeekContextController implements IEditorContribution { 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..854aff5d9f9 --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -0,0 +1,486 @@ +/*--------------------------------------------------------------------------------------------- + * 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 = Object.create(null)) { + super(options); + + 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}`; + const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length; + + 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) { + + // First: try to score on the entire query, it is possible that + // the symbol matches perfectly (e.g. searching for "change log" + // can be a match on a markdown symbol "change log"). In that + // case we want to skip the container query altogether. + let skipContainerQuery = false; + if (symbolQuery !== query) { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, query, filterPos, symbolLabelIconOffset); + if (symbolScore) { + skipContainerQuery = true; // since we consumed the query, skip any container matching + } + } + + // Otherwise: score on the symbol query and match on the container later + if (!symbolScore) { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelIconOffset); + if (!symbolScore) { + continue; + } + } + + // Score by container if specified + if (!skipContainerQuery && 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 a6320e959bf..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. *--------------------------------------------------------------------------------------------*/ -.explorer-viewlet .explorer-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/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 28aba650e6a..360986dd666 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -206,7 +206,8 @@ class RenameController implements IEditorContribution { this._bulkEditService.apply(renameResult, { editor: this.editor, showPreview: inputFieldResult.wantsPreview, - label: nls.localize('label', "Renaming '{0}'", loc?.text) + label: nls.localize('label', "Renaming '{0}'", loc?.text), + quotableLabel: nls.localize('quotableLabel', "Renaming {0}", loc?.text), }).then(result => { if (result.ariaSummary) { alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, inputFieldResult.newName, result.ariaSummary)); 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 2b19d53b201..4c17969b9cd 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -19,6 +19,9 @@ import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSe import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test'; 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 { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; class MockJSMode extends MockMode { @@ -47,7 +50,8 @@ suite('SmartSelect', () => { setup(() => { const configurationService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService()); + const dialogService = new TestDialogService(); + modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService); mode = new MockJSMode(); }); @@ -180,9 +184,11 @@ suite('SmartSelect', () => { // -- bracket selections async function assertRanges(provider: SelectionRangeProvider, value: string, ...expected: IRange[]): Promise { + let index = value.indexOf('|'); + value = value.replace('|', ''); let model = modelService.createModel(value, new StaticLanguageSelector(mode.getLanguageIdentifier()), URI.parse('fake:lang')); - let pos = model.getPositionAt(value.indexOf('|')); + let pos = model.getPositionAt(index); let all = await provider.provideSelectionRanges(model, [pos], CancellationToken.None); let ranges = all![0]; @@ -197,18 +203,18 @@ suite('SmartSelect', () => { test('bracket selection', async () => { await assertRanges(new BracketSelectionRangeProvider(), '(|)', - new Range(1, 2, 1, 3), new Range(1, 1, 1, 4) + new Range(1, 2, 1, 2), new Range(1, 1, 1, 3) ); await assertRanges(new BracketSelectionRangeProvider(), '[[[](|)]]', - new Range(1, 6, 1, 7), new Range(1, 5, 1, 8), // () - new Range(1, 3, 1, 8), new Range(1, 2, 1, 9), // [[]()] - new Range(1, 2, 1, 9), new Range(1, 1, 1, 10), // [[[]()]] + new Range(1, 6, 1, 6), new Range(1, 5, 1, 7), // () + new Range(1, 3, 1, 7), new Range(1, 2, 1, 8), // [[]()] + new Range(1, 2, 1, 8), new Range(1, 1, 1, 9), // [[[]()]] ); await assertRanges(new BracketSelectionRangeProvider(), '[a[](|)a]', - new Range(1, 6, 1, 7), new Range(1, 5, 1, 8), - new Range(1, 2, 1, 9), new Range(1, 1, 1, 10), + new Range(1, 6, 1, 6), new Range(1, 5, 1, 7), + new Range(1, 2, 1, 8), new Range(1, 1, 1, 9), ); // no bracket @@ -219,23 +225,23 @@ suite('SmartSelect', () => { await assertRanges(new BracketSelectionRangeProvider(), '|[[[]()]]'); // edge - await assertRanges(new BracketSelectionRangeProvider(), '[|[[]()]]', new Range(1, 2, 1, 9), new Range(1, 1, 1, 10)); - await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]|]', new Range(1, 2, 1, 9), new Range(1, 1, 1, 10)); + await assertRanges(new BracketSelectionRangeProvider(), '[|[[]()]]', new Range(1, 2, 1, 8), new Range(1, 1, 1, 9)); + await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]|]', new Range(1, 2, 1, 8), new Range(1, 1, 1, 9)); - await assertRanges(new BracketSelectionRangeProvider(), 'aaa(aaa)bbb(b|b)ccc(ccc)', new Range(1, 13, 1, 16), new Range(1, 12, 1, 17)); - await assertRanges(new BracketSelectionRangeProvider(), '(aaa(aaa)bbb(b|b)ccc(ccc))', new Range(1, 14, 1, 17), new Range(1, 13, 1, 18), new Range(1, 2, 1, 26), new Range(1, 1, 1, 27)); + await assertRanges(new BracketSelectionRangeProvider(), 'aaa(aaa)bbb(b|b)ccc(ccc)', new Range(1, 13, 1, 15), new Range(1, 12, 1, 16)); + await assertRanges(new BracketSelectionRangeProvider(), '(aaa(aaa)bbb(b|b)ccc(ccc))', new Range(1, 14, 1, 16), new Range(1, 13, 1, 17), new Range(1, 2, 1, 25), new Range(1, 1, 1, 26)); }); test('bracket with leading/trailing', async () => { await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b){\n foo(|);\n}', - new Range(2, 7, 2, 8), new Range(2, 6, 2, 9), + new Range(2, 7, 2, 7), new Range(2, 6, 2, 8), new Range(1, 13, 3, 1), new Range(1, 12, 3, 2), new Range(1, 1, 3, 2), new Range(1, 1, 3, 2), ); await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b)\n{\n foo(|);\n}', - new Range(3, 7, 3, 8), new Range(3, 6, 3, 9), + new Range(3, 7, 3, 7), new Range(3, 6, 3, 8), new Range(2, 2, 4, 1), new Range(2, 1, 4, 2), new Range(1, 1, 4, 2), new Range(1, 1, 4, 2), ); @@ -244,60 +250,60 @@ suite('SmartSelect', () => { test('in-word ranges', async () => { await assertRanges(new WordSelectionRangeProvider(), 'f|ooBar', - new Range(1, 1, 1, 5), // foo - new Range(1, 1, 1, 8), // fooBar - new Range(1, 1, 1, 8), // doc + new Range(1, 1, 1, 4), // foo + new Range(1, 1, 1, 7), // fooBar + new Range(1, 1, 1, 7), // doc ); await assertRanges(new WordSelectionRangeProvider(), 'f|oo_Ba', - new Range(1, 1, 1, 5), - new Range(1, 1, 1, 8), - new Range(1, 1, 1, 8), + new Range(1, 1, 1, 4), + new Range(1, 1, 1, 7), + new Range(1, 1, 1, 7), ); await assertRanges(new WordSelectionRangeProvider(), 'f|oo-Ba', - new Range(1, 1, 1, 5), - new Range(1, 1, 1, 8), - new Range(1, 1, 1, 8), + new Range(1, 1, 1, 4), + new Range(1, 1, 1, 7), + new Range(1, 1, 1, 7), ); }); test('Default selection should select current word/hump first in camelCase #67493', async function () { await assertRanges(new WordSelectionRangeProvider(), 'Abs|tractSmartSelect', - new Range(1, 1, 1, 10), - new Range(1, 1, 1, 21), - new Range(1, 1, 1, 21), + new Range(1, 1, 1, 9), + new Range(1, 1, 1, 20), + new Range(1, 1, 1, 20), ); await assertRanges(new WordSelectionRangeProvider(), 'AbstractSma|rtSelect', - new Range(1, 9, 1, 15), - new Range(1, 1, 1, 21), - new Range(1, 1, 1, 21), + new Range(1, 9, 1, 14), + new Range(1, 1, 1, 20), + new Range(1, 1, 1, 20), ); await assertRanges(new WordSelectionRangeProvider(), 'Abstrac-Sma|rt-elect', - new Range(1, 9, 1, 15), - new Range(1, 1, 1, 21), - new Range(1, 1, 1, 21), + new Range(1, 9, 1, 14), + new Range(1, 1, 1, 20), + new Range(1, 1, 1, 20), ); await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt_elect', - new Range(1, 9, 1, 15), - new Range(1, 1, 1, 21), - new Range(1, 1, 1, 21), + new Range(1, 9, 1, 14), + new Range(1, 1, 1, 20), + new Range(1, 1, 1, 20), ); await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt-elect', - new Range(1, 9, 1, 15), - new Range(1, 1, 1, 21), - new Range(1, 1, 1, 21), + new Range(1, 9, 1, 14), + new Range(1, 1, 1, 20), + new Range(1, 1, 1, 20), ); await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rtSelect', - new Range(1, 9, 1, 15), - new Range(1, 1, 1, 21), - new Range(1, 1, 1, 21), + new Range(1, 9, 1, 14), + new Range(1, 1, 1, 20), + new Range(1, 1, 1, 20), ); }); @@ -321,4 +327,49 @@ suite('SmartSelect', () => { reg.dispose(); }); + + test('Expand selection in words with underscores is inconsistent #90589', async function () { + + await assertRanges(new WordSelectionRangeProvider(), 'Hel|lo_World', + new Range(1, 1, 1, 6), + new Range(1, 1, 1, 12), + new Range(1, 1, 1, 12), + ); + + await assertRanges(new WordSelectionRangeProvider(), 'Hello_Wo|rld', + new Range(1, 7, 1, 12), + new Range(1, 1, 1, 12), + new Range(1, 1, 1, 12), + ); + + await assertRanges(new WordSelectionRangeProvider(), 'Hello|_World', + new Range(1, 1, 1, 6), + new Range(1, 1, 1, 12), + new Range(1, 1, 1, 12), + ); + + await assertRanges(new WordSelectionRangeProvider(), 'Hello_|World', + new Range(1, 7, 1, 12), + new Range(1, 1, 1, 12), + new Range(1, 1, 1, 12), + ); + + await assertRanges(new WordSelectionRangeProvider(), 'Hello|-World', + new Range(1, 1, 1, 6), + new Range(1, 1, 1, 12), + new Range(1, 1, 1, 12), + ); + + await assertRanges(new WordSelectionRangeProvider(), 'Hello-|World', + new Range(1, 7, 1, 12), + new Range(1, 1, 1, 12), + new Range(1, 1, 1, 12), + ); + + await assertRanges(new WordSelectionRangeProvider(), 'Hello|World', + new Range(1, 6, 1, 11), + new Range(1, 1, 1, 11), + new Range(1, 1, 1, 11), + ); + }); }); diff --git a/src/vs/editor/contrib/smartSelect/wordSelections.ts b/src/vs/editor/contrib/smartSelect/wordSelections.ts index 7402202af54..280663f59a8 100644 --- a/src/vs/editor/contrib/smartSelect/wordSelections.ts +++ b/src/vs/editor/contrib/smartSelect/wordSelections.ts @@ -40,7 +40,7 @@ export class WordSelectionRangeProvider implements SelectionRangeProvider { // LEFT anchor (start) for (; start >= 0; start--) { let ch = word.charCodeAt(start); - if (ch === CharCode.Underline || ch === CharCode.Dash) { + if ((start !== offset) && (ch === CharCode.Underline || ch === CharCode.Dash)) { // foo-bar OR foo_bar break; } else if (isLowerAsciiLetter(ch) && isUpperAsciiLetter(lastCh)) { 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/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index 29703e9307a..4f67e00ac4e 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -12,6 +12,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { NullLogService } from 'vs/platform/log/common/log'; import { Handler } from 'vs/editor/common/editorCommon'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('SnippetController2', function () { @@ -36,7 +37,7 @@ suite('SnippetController2', function () { setup(function () { contextKeys = new MockContextKeyService(); - model = TextModel.createFromString('if\n $state\nfi'); + model = createTextModel('if\n $state\nfi'); editor = createTestCodeEditor({ model: model }); editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]); assert.equal(model.getEOL(), '\n'); diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index d4ae07a4a49..cedf6be949e 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -11,6 +11,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; import { SnippetSession } from 'vs/editor/contrib/snippet/snippetSession'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('SnippetSession', function () { @@ -26,7 +27,7 @@ suite('SnippetSession', function () { } setup(function () { - model = TextModel.createFromString('function foo() {\n console.log(a);\n}'); + model = createTextModel('function foo() {\n console.log(a);\n}'); editor = createTestCodeEditor({ model: model }) as IActiveCodeEditor; editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]); assert.equal(model.getEOL(), '\n'); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 06eb5f8777c..b7012bec477 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -12,6 +12,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { Workspace, toWorkspaceFolders, IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ILabelService } from 'vs/platform/label/common/label'; import { mock } from 'vs/editor/contrib/suggest/test/suggestModel.test'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Snippet Variables Resolver', function () { @@ -25,7 +26,7 @@ suite('Snippet Variables Resolver', function () { let resolver: VariableResolver; setup(function () { - model = TextModel.createFromString([ + model = createTextModel([ 'this is line one', 'this is line two', ' this is line three' @@ -67,7 +68,7 @@ suite('Snippet Variables Resolver', function () { resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) + createTextModel('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) ); assertVariableResolve(resolver, 'TM_FILENAME', 'ghi'); if (!isWindows) { @@ -77,7 +78,7 @@ suite('Snippet Variables Resolver', function () { resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('mem:fff.ts')) + createTextModel('', undefined, undefined, URI.parse('mem:fff.ts')) ); assertVariableResolve(resolver, 'TM_DIRECTORY', ''); assertVariableResolve(resolver, 'TM_FILEPATH', 'fff.ts'); @@ -92,7 +93,7 @@ suite('Snippet Variables Resolver', function () { } }; - const model = TextModel.createFromString([].join('\n'), undefined, undefined, URI.parse('foo:///foo/files/text.txt')); + const model = createTextModel([].join('\n'), undefined, undefined, URI.parse('foo:///foo/files/text.txt')); const resolver = new CompositeSnippetVariableResolver([new ModelBasedVariableResolver(labelService, model)]); @@ -144,19 +145,19 @@ suite('Snippet Variables Resolver', function () { resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) + createTextModel('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'ghi'); resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('mem:.git')) + createTextModel('', undefined, undefined, URI.parse('mem:.git')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', '.git'); resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('mem:foo.')) + createTextModel('', undefined, undefined, URI.parse('mem:foo.')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'foo'); }); diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 821cb7d2623..be783ef5177 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -176,7 +176,7 @@ } .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .signature-label { - overflow: auto; + overflow: hidden; text-overflow: ellipsis; } @@ -228,13 +228,14 @@ .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left { flex-shrink: 1; + flex-grow: 1; overflow: hidden; } .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .monaco-icon-label { flex-shrink: 0; } .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main > .left > .monaco-icon-label { - max-width: 80%; + max-width: 100%; } .monaco-editor .suggest-widget .monaco-list .monaco-list-row.string-label > .contents > .main > .left > .monaco-icon-label { flex-shrink: 1; @@ -392,6 +393,10 @@ word-wrap: break-word; } +.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs .codicon { + vertical-align: sub; +} + .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > p:empty { display: none; } 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 0056a622743..00000000000 --- a/src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts +++ /dev/null @@ -1,125 +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'; - -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(); - 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 5baa237e29a..0e3725fc5d4 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` @@ -144,15 +144,14 @@ 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'; @@ -231,7 +229,7 @@ class ItemRenderer implements IListRenderer= 0) { @@ -241,12 +239,12 @@ class ItemRenderer implements IListRenderer | null = null; @@ -552,7 +551,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate toggleClass(this.element, 'with-status-bar', !this.editor.getOption(EditorOption.suggest).hideStatusBar); + const applyStatusBarStyle = () => toggleClass(this.element, 'with-status-bar', this.editor.getOption(EditorOption.suggest).statusBar.visible); applyStatusBarStyle(); this.statusBarElement = append(this.element, $('.suggest-status-bar')); @@ -605,7 +604,9 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate false }, mouseSupport: false, + ariaRole: 'listbox', accessibilityProvider: { + getRole: () => 'option', getAriaLabel: (item: CompletionItem) => { const textLabel = typeof item.completion.label === 'string' ? item.completion.label : item.completion.label.name; if (item.isResolved && this.expandDocsSettingFromStorage()) { @@ -627,12 +628,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)) { @@ -645,10 +646,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { this._onDetailsKeydown.fire(e); @@ -715,7 +713,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { new(): T; @@ -124,7 +125,7 @@ suite('SuggestModel - Context', function () { }); test('Context - shouldAutoTrigger', function () { - const model = TextModel.createFromString('Das Pferd frisst keinen Gurkensalat - Philipp Reis 1861.\nWer hat\'s erfunden?'); + const model = createTextModel('Das Pferd frisst keinen Gurkensalat - Philipp Reis 1861.\nWer hat\'s erfunden?'); disposables.push(model); assertAutoTrigger(model, 3, true, 'end of word, Das|'); @@ -138,7 +139,7 @@ suite('SuggestModel - Context', function () { const innerMode = new InnerMode(); disposables.push(outerMode, innerMode); - const model = TextModel.createFromString('aa', undefined, outerMode.getLanguageIdentifier()); + const model = createTextModel('aa', undefined, outerMode.getLanguageIdentifier()); disposables.push(model); assertAutoTrigger(model, 1, true, 'a|(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/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index 0fa9fe4caa7..2b1d99f2aa9 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -11,6 +11,9 @@ import { Selection } from 'vs/editor/common/core/selection'; import { deserializePipePositions, serializePipePositions, testRepeatedActionAndExtractPositions } from 'vs/editor/contrib/wordOperations/test/wordTestUtils'; import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorWordEndRightSelect, CursorWordLeft, CursorWordLeftSelect, CursorWordRight, CursorWordRightSelect, CursorWordStartLeft, CursorWordStartLeftSelect, CursorWordStartRight, CursorWordStartRightSelect, DeleteWordEndLeft, DeleteWordEndRight, DeleteWordLeft, DeleteWordRight, DeleteWordStartLeft, DeleteWordStartRight, CursorWordAccessibilityLeft, CursorWordAccessibilityLeftSelect, CursorWordAccessibilityRight, CursorWordAccessibilityRightSelect } from 'vs/editor/contrib/wordOperations/wordOperations'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { Handler } from 'vs/editor/common/editorCommon'; +import { Cursor } from 'vs/editor/common/controller/cursor'; +import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; suite('WordOperations', () => { @@ -193,6 +196,32 @@ suite('WordOperations', () => { assert.deepEqual(actual, EXPECTED); }); + test('issue #51275 - cursorWordStartLeft does not push undo/redo stack element', () => { + function cursorCommand(cursor: Cursor, command: string, extraData?: any, overwriteSource?: string) { + cursor.trigger(overwriteSource || 'tests', command, extraData); + } + + function type(cursor: Cursor, text: string) { + for (let i = 0; i < text.length; i++) { + cursorCommand(cursor, Handler.Type, { text: text.charAt(i) }, 'keyboard'); + } + } + + withTestCodeEditor('', {}, (editor, cursor) => { + type(cursor, 'foo bar baz'); + assert.equal(editor.getValue(), 'foo bar baz'); + + cursorWordStartLeft(editor); + cursorWordStartLeft(editor); + type(cursor, 'q'); + + assert.equal(editor.getValue(), 'foo qbar baz'); + + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + assert.equal(editor.getValue(), 'foo bar baz'); + }); + }); + test('cursorWordEndLeft', () => { const EXPECTED = ['| /*| Just| some| more| text| a|+=| 3| +|5|-|3| +| 7| */| '].join('\n'); const [text,] = deserializePipePositions(EXPECTED); diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index 57d9174719d..ae5429ed0cc 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -52,6 +52,7 @@ export abstract class MoveWordCommand extends EditorCommand { return this._moveTo(sel, outPosition, this._inSelectionMode); }); + model.pushStackElement(); editor._getCursors().setStates('moveWordCommand', CursorChangeReason.NotSet, result.map(r => CursorState.fromModelSelection(r))); if (result.length === 1) { const pos = new Position(result[0].positionLineNumber, result[0].positionColumn); 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/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 3c32536b239..08d629e4260 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -6,6 +6,7 @@ import 'vs/css!./inspectTokens'; import { CharCode } from 'vs/base/common/charCode'; import { Color } from 'vs/base/common/color'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { escape } from 'vs/base/common/strings'; import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; @@ -48,6 +49,7 @@ class InspectTokensController extends Disposable implements IEditorContribution this._register(this._editor.onDidChangeModel((e) => this.stop())); this._register(this._editor.onDidChangeModelLanguage((e) => this.stop())); this._register(TokenizationRegistry.onDidChange((e) => this.stop())); + this._register(this._editor.onKeyUp((e) => e.keyCode === KeyCode.Escape && this.stop())); } public dispose(): void { @@ -222,13 +224,13 @@ class InspectTokensWidget extends Disposable implements IContentWidget { result += `
`; - let metadata = this._decodeMetadata(data.tokens2[(token2Index << 1) + 1]); + let metadata = (token2Index << 1) + 1 < data.tokens2.length ? this._decodeMetadata(data.tokens2[(token2Index << 1) + 1]) : null; result += ``; - result += ``; - result += ``; - result += ``; - result += ``; - result += ``; + result += ``; + result += ``; + result += ``; + result += ``; + result += ``; result += ``; result += `
`; 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..4a7c020fae7 --- /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<(O extends { canPickMany: true } ? T[] : T) | undefined> { + 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 773523bab16..37b97c11c88 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -24,10 +24,10 @@ 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 } from 'vs/platform/configuration/common/configurationModels'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; @@ -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`)); } @@ -318,7 +309,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { })); } - public addDynamicKeybinding(commandId: string, _keybinding: number, handler: ICommandHandler, when: ContextKeyExpr | undefined): IDisposable { + public addDynamicKeybinding(commandId: string, _keybinding: number, handler: ICommandHandler, when: ContextKeyExpression | undefined): IDisposable { const keybinding = createKeybinding(_keybinding, OS); const toDispose = new DisposableStore(); @@ -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; @@ -448,10 +432,6 @@ export class SimpleConfigurationService implements IConfigurationService { this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); } - private configuration(): Configuration { - return this._configuration; - } - getValue(): T; getValue(section: string): T; getValue(overrides: IConfigurationOverrides): T; @@ -459,20 +439,43 @@ export class SimpleConfigurationService implements IConfigurationService { getValue(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : undefined; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {}; - return this.configuration().getValue(section, overrides, undefined); + return this._configuration.getValue(section, overrides, undefined); } - public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise { - this.configuration().updateValue(key, value); + public updateValues(values: [string, any][]): Promise { + const previous = { data: this._configuration.toData() }; + + let changedKeys: string[] = []; + + for (const entry of values) { + const [key, value] = entry; + if (this.getValue(key) === value) { + continue; + } + this._configuration.updateValue(key, value); + changedKeys.push(key); + } + + if (changedKeys.length > 0) { + const configurationChangeEvent = new ConfigurationChangeEvent({ keys: changedKeys, overrides: [] }, previous, this._configuration); + configurationChangeEvent.source = ConfigurationTarget.MEMORY; + configurationChangeEvent.sourceConfig = null; + this._onDidChangeConfiguration.fire(configurationChangeEvent); + } + return Promise.resolve(); } + public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise { + return this.updateValues([[key, value]]); + } + public inspect(key: string, options: IConfigurationOverrides = {}): IConfigurationValue { - return this.configuration().inspect(key, options, undefined); + return this._configuration.inspect(key, options, undefined); } public keys() { - return this.configuration().keys(undefined); + return this._configuration.keys(undefined); } public reloadConfiguration(): Promise { @@ -622,14 +625,18 @@ export function applyConfigurationValues(configurationService: IConfigurationSer if (!(configurationService instanceof SimpleConfigurationService)) { return; } + let toUpdate: [string, any][] = []; Object.keys(source).forEach((key) => { if (isEditorConfigurationKey(key)) { - configurationService.updateValue(`editor.${key}`, source[key]); + toUpdate.push([`editor.${key}`, source[key]]); } if (isDiffEditor && isDiffEditorConfigurationKey(key)) { - configurationService.updateValue(`diffEditor.${key}`, source[key]); + toUpdate.push([`diffEditor.${key}`, source[key]]); } }); + if (toUpdate.length > 0) { + configurationService.updateValues(toUpdate); + } } export class SimpleBulkEditService implements IBulkEditService { @@ -690,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') { @@ -726,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); } @@ -739,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 7f1e39821e7..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'; @@ -23,7 +22,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { IInstantiationService, optional, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -114,6 +113,11 @@ export interface IGlobalEditorOptions { * Defaults to true. */ wordBasedSuggestions?: boolean; + /** + * Controls whether the semanticHighlighting is shown for the languages that support it. + * Defaults to true. + */ + 'semanticHighlighting.enabled'?: boolean; /** * Keep peek editors open even when double clicking their content or when hitting `Escape`. * Defaults to false. @@ -124,6 +128,13 @@ export interface IGlobalEditorOptions { * Defaults to 20000. */ maxTokenizationLineLength?: number; + /** + * Theme to be used for rendering. + * The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black'. + * You can create custom themes via `monaco.editor.defineTheme`. + * To switch a theme, use `monaco.editor.setTheme` + */ + theme?: string; } /** @@ -221,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) { @@ -334,6 +341,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon private readonly _contextViewService: ContextViewService; private readonly _configurationService: IConfigurationService; + private readonly _standaloneThemeService: IStandaloneThemeService; private _ownsModel: boolean; constructor( @@ -363,6 +371,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon this._contextViewService = contextViewService; this._configurationService = configurationService; + this._standaloneThemeService = themeService; this._register(toDispose); this._register(themeDomRegistration); @@ -391,6 +400,9 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon public updateOptions(newOptions: IEditorOptions & IGlobalEditorOptions): void { applyConfigurationValues(this._configurationService, newOptions, false); + if (typeof newOptions.theme === 'string') { + this._standaloneThemeService.setTheme(newOptions.theme); + } super.updateOptions(newOptions); } @@ -414,6 +426,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon private readonly _contextViewService: ContextViewService; private readonly _configurationService: IConfigurationService; + private readonly _standaloneThemeService: IStandaloneThemeService; constructor( domElement: HTMLElement, @@ -430,7 +443,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IEditorProgressService editorProgressService: IEditorProgressService, - @optional(IClipboardService) clipboardService: IClipboardService | null, + @IClipboardService clipboardService: IClipboardService, ) { applyConfigurationValues(configurationService, options, true); const themeDomRegistration = (themeService).registerEditorContainer(domElement); @@ -443,6 +456,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon this._contextViewService = contextViewService; this._configurationService = configurationService; + this._standaloneThemeService = themeService; this._register(toDispose); this._register(themeDomRegistration); @@ -454,8 +468,11 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon super.dispose(); } - public updateOptions(newOptions: IDiffEditorOptions): void { + public updateOptions(newOptions: IDiffEditorOptions & IGlobalEditorOptions): void { applyConfigurationValues(this._configurationService, newOptions, true); + if (typeof newOptions.theme === 'string') { + this._standaloneThemeService.setTheme(newOptions.theme); + } super.updateOptions(newOptions); } 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/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index e35ab13d4f4..a7b3f43dba8 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -39,6 +39,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; type Omit = Pick>; @@ -122,7 +123,7 @@ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorC services.get(IConfigurationService), services.get(IContextMenuService), services.get(IEditorProgressService), - null + services.get(IClipboardService) ); }); } 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 44581138ad5..9864cf06a14 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -48,6 +48,13 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; +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; @@ -148,7 +155,9 @@ export module StaticServices { export const logService = define(ILogService, () => new NullLogService()); - export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o))); + 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), dialogService.get(o))); export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); @@ -158,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))); } @@ -200,10 +211,14 @@ 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))); + ensure(IClipboardService, () => new BrowserClipboardService()); + ensure(IContextMenuService, () => { const contextMenuService = new ContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, themeService); contextMenuService.configure({ blockMouse: false }); // we do not want that in the standalone editor 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/commands/sideEditing.test.ts b/src/vs/editor/test/browser/commands/sideEditing.test.ts index 540771d0c72..49514eef350 100644 --- a/src/vs/editor/test/browser/commands/sideEditing.test.ts +++ b/src/vs/editor/test/browser/commands/sideEditing.test.ts @@ -10,7 +10,7 @@ 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 { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; @@ -199,7 +199,7 @@ suite('SideEditing', () => { ]; function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void { - const model = TextModel.createFromString(LINES.join('\n')); + const model = createTextModel(LINES.join('\n')); const config = new TestConfiguration({}); const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(config.options); const viewModel = new ViewModel(0, config, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index c4b0df1d3cb..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', () => { @@ -1240,22 +1259,22 @@ suite('Editor Controller - Regression tests', () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert9'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert10'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert11'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert12'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert13'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert14'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert15'); }); @@ -1263,12 +1282,12 @@ suite('Editor Controller - Regression tests', () => { }); test('issue #23539: Setting model EOL isn\'t undoable', () => { - usingCursor({ - text: [ - 'Hello', - 'world' - ] - }, (model, cursor) => { + withTestCodeEditor([ + 'Hello', + 'world' + ], {}, (editor, cursor) => { + const model = editor.getModel()!; + assertCursor(cursor, new Position(1, 1)); model.setEOL(EndOfLineSequence.LF); assert.equal(model.getValue(), 'Hello\nworld'); @@ -1276,7 +1295,7 @@ suite('Editor Controller - Regression tests', () => { model.pushEOL(EndOfLineSequence.CRLF); assert.equal(model.getValue(), 'Hello\r\nworld'); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(), 'Hello\nworld'); }); }); @@ -1301,7 +1320,7 @@ suite('Editor Controller - Regression tests', () => { cursorCommand(cursor, H.Type, { text: '%' }, 'keyboard'); assert.equal(model.getValue(EndOfLinePreference.LF), '%\'%👁\'', 'assert1'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\'', 'assert2'); }); @@ -1327,39 +1346,39 @@ suite('Editor Controller - Regression tests', () => { assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); - cursorCommand(cursor, H.Undo, {}); + 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)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello'); assertCursor(cursor, new Position(1, 6)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), ''); assertCursor(cursor, new Position(1, 1)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello'); assertCursor(cursor, new Position(1, 6)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world '); assertCursor(cursor, new Position(1, 13)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'Hello world'); assertCursor(cursor, new Position(1, 12)); }); @@ -1735,21 +1754,21 @@ suite('Editor Controller - Regression tests', () => { '\t just some text' ].join('\n'), '001'); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(), [ ' some lines', ' and more lines', ' just some text', ].join('\n'), '002'); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(), [ 'some lines', 'and more lines', 'just some text', ].join('\n'), '003'); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(), [ 'some lines', 'and more lines', @@ -1935,10 +1954,8 @@ suite('Editor Controller - Regression tests', () => { }); test('issue #9675: Undo/Redo adds a stop in between CHN Characters', () => { - usingCursor({ - text: [ - ] - }, (model, cursor) => { + withTestCodeEditor([], {}, (editor, cursor) => { + const model = editor.getModel()!; assertCursor(cursor, new Position(1, 1)); // Typing sennsei in Japanese - Hiragana @@ -1957,7 +1974,7 @@ suite('Editor Controller - Regression tests', () => { assert.equal(model.getLineContent(1), 'せんせい'); assertCursor(cursor, new Position(1, 5)); - cursorCommand(cursor, H.Undo); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), ''); assertCursor(cursor, new Position(1, 1)); }); @@ -2138,7 +2155,7 @@ suite('Editor Controller - Regression tests', () => { }], () => [new Selection(1, 1, 1, 1)]); assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!'); }); @@ -2229,12 +2246,12 @@ suite('Editor Controller - Regression tests', () => { new Selection(1, 5, 1, 5), ]); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assertCursor(cursor, [ new Selection(1, 4, 1, 4), ]); - cursorCommand(cursor, H.Redo, null, 'keyboard'); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assertCursor(cursor, [ new Selection(1, 5, 1, 5), ]); @@ -2263,7 +2280,7 @@ suite('Editor Controller - Regression tests', () => { new Selection(1, 1, 1, 1), ]); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assertCursor(cursor, [ new Selection(1, 1, 1, 1), ]); @@ -2378,49 +2395,49 @@ suite('Editor Controller - Cursor Configuration', () => { CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 1) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), ' My Second Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 2 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 2) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'M y Second Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 3 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 3) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My Second Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 4 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 4) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My Second Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 5 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My S econd Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 5 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My S econd Line123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 13 assert.equal(model.getLineContent(2), 'My Second Line123'); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 13) }); CoreEditingCommands.Tab.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'My Second Li ne123'); - cursorCommand(cursor, H.Undo, null, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); // Tab on column 14 assert.equal(model.getLineContent(2), 'My Second Line123'); @@ -2774,7 +2791,7 @@ suite('Editor Controller - Cursor Configuration', () => { assert.equal(model.getLineContent(2), 'a '); // Undo DeleteLeft - get us back to original indentation - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), ' a '); // Nothing is broken when cursor is in (1,1) @@ -2859,27 +2876,54 @@ suite('Editor Controller - Cursor Configuration', () => { CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert10'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert11'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert12'); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert13'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert14'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert15'); - cursorCommand(cursor, H.Redo, {}); + CoreEditingCommands.Redo.runEditorCommand(null, editor, null); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert16'); }); model.dispose(); }); + + test('issue #90973: Undo brings back model alternative version', () => { + let model = createTextModel( + [ + '' + ].join('\n'), + { + insertSpaces: false, + } + ); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + const beforeVersion = model.getVersionId(); + const beforeAltVersion = model.getAlternativeVersionId(); + cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard'); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + const afterVersion = model.getVersionId(); + const afterAltVersion = model.getAlternativeVersionId(); + + assert.notEqual(beforeVersion, afterVersion); + assert.equal(beforeAltVersion, afterAltVersion); + }); + + model.dispose(); + }); + + }); suite('Editor Controller - Indentation Rules', () => { @@ -4236,7 +4280,7 @@ suite('autoClosingPairs', () => { moveTo(cursor, lineNumber, column); cursorCommand(cursor, H.Type, { text: chr }, 'keyboard'); assert.deepEqual(model.getLineContent(lineNumber), expected, message); - cursorCommand(cursor, H.Undo); + model.undo(); } test('open parens: default', () => { @@ -5019,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({ @@ -5320,11 +5386,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(1), 'A fir line'); assertCursor(cursor, new Selection(1, 6, 1, 6)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A first line'); assertCursor(cursor, new Selection(1, 8, 1, 8)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A line'); assertCursor(cursor, new Selection(1, 3, 1, 3)); }); @@ -5349,11 +5415,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(1), 'A firstine'); assertCursor(cursor, new Selection(1, 8, 1, 8)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A first line'); assertCursor(cursor, new Selection(1, 8, 1, 8)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A line'); assertCursor(cursor, new Selection(1, 3, 1, 3)); }); @@ -5383,11 +5449,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(2), 'Second line'); assertCursor(cursor, new Selection(2, 7, 2, 7)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), ' line'); assertCursor(cursor, new Selection(2, 1, 2, 1)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another line'); assertCursor(cursor, new Selection(2, 8, 2, 8)); }); @@ -5421,11 +5487,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(2), ''); assertCursor(cursor, new Selection(2, 1, 2, 1)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), ' line'); assertCursor(cursor, new Selection(2, 1, 2, 1)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another line'); assertCursor(cursor, new Selection(2, 8, 2, 8)); }); @@ -5452,11 +5518,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(2), 'Another text'); assertCursor(cursor, new Selection(2, 13, 2, 13)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another '); assertCursor(cursor, new Selection(2, 9, 2, 9)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another line'); assertCursor(cursor, new Selection(2, 9, 2, 9)); }); @@ -5488,11 +5554,11 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(2), 'An'); assertCursor(cursor, new Selection(2, 3, 2, 3)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another '); assertCursor(cursor, new Selection(2, 9, 2, 9)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(2), 'Another line'); assertCursor(cursor, new Selection(2, 9, 2, 9)); }); @@ -5512,18 +5578,62 @@ suite('Undo stops', () => { assert.equal(model.getLineContent(1), 'A first and interesting line'); assertCursor(cursor, new Selection(1, 24, 1, 24)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A first and line'); assertCursor(cursor, new Selection(1, 12, 1, 12)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A first line'); assertCursor(cursor, new Selection(1, 8, 1, 8)); - cursorCommand(cursor, H.Undo, {}); + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); assert.equal(model.getLineContent(1), 'A line'); assertCursor(cursor, new Selection(1, 3, 1, 3)); }); }); + test('can undo typing and EOL change in one undo stop', () => { + let model = createTextModel( + [ + 'A line', + 'Another line', + ].join('\n') + ); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + cursor.setSelections('test', [new Selection(1, 3, 1, 3)]); + cursorCommand(cursor, H.Type, { text: 'first' }, 'keyboard'); + assert.equal(model.getValue(), 'A first line\nAnother line'); + assertCursor(cursor, new Selection(1, 8, 1, 8)); + + model.pushEOL(EndOfLineSequence.CRLF); + assert.equal(model.getValue(), 'A first line\r\nAnother line'); + assertCursor(cursor, new Selection(1, 8, 1, 8)); + + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + assert.equal(model.getValue(), 'A line\nAnother line'); + assertCursor(cursor, new Selection(1, 3, 1, 3)); + }); + }); + + 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/controller/cursorMoveCommand.test.ts b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts index 36f2f54dc23..e918feacef0 100644 --- a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts +++ b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts @@ -14,6 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Cursor move command test', () => { @@ -31,7 +32,7 @@ suite('Cursor move command test', () => { '1' ].join('\n'); - thisModel = TextModel.createFromString(text); + thisModel = createTextModel(text); thisConfiguration = new TestConfiguration({}); const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(thisConfiguration.options); thisViewModel = new ViewModel(0, thisConfiguration, thisModel, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index 005a30b465f..163e9de5422 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ITextAreaWrapper, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; export class MockTextAreaWrapper extends Disposable implements ITextAreaWrapper { @@ -506,7 +506,7 @@ suite('TextAreaState', () => { suite('PagedScreenReaderStrategy', () => { function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void { - const model = TextModel.createFromString(lines.join('\n')); + const model = createTextModel(lines.join('\n')); const actual = PagedScreenReaderStrategy.fromEditorSelection(TextAreaState.EMPTY, model, selection, 10, true); assert.ok(equalsTextAreaState(actual, expected)); model.dispose(); 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 47a341a2d63..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 { TextModel } from 'vs/editor/common/model/textModel'; 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; } @@ -77,13 +78,14 @@ export function withTestCodeEditor(text: string | string[] | null, options: Test // create a model if necessary and remember it in order to dispose it. if (!options.model) { if (typeof text === 'string') { - options.model = TextModel.createFromString(text); + options.model = createTextModel(text); } else if (text) { - options.model = TextModel.createFromString(text.join('\n')); + options.model = createTextModel(text.join('\n')); } } let editor = createTestCodeEditor(options); + editor.getCursor()!.setHasFocus(true); callback(editor, editor.getCursor()!); editor.dispose(); diff --git a/src/vs/editor/test/browser/testCommand.ts b/src/vs/editor/test/browser/testCommand.ts index 8126f24735a..4c280c1d960 100644 --- a/src/vs/editor/test/browser/testCommand.ts +++ b/src/vs/editor/test/browser/testCommand.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; +import { IRange } from 'vs/editor/common/core/range'; +import { Selection, ISelection } from 'vs/editor/common/core/selection'; import { ICommand, Handler, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; @@ -21,7 +21,7 @@ export function testCommand( expectedSelection: Selection, forceTokenization?: boolean ): void { - let model = TextModel.createFromString(lines.join('\n'), undefined, languageIdentifier); + let model = createTextModel(lines.join('\n'), undefined, languageIdentifier); withTestCodeEditor('', { model: model }, (_editor, cursor) => { if (!cursor) { return; @@ -50,7 +50,7 @@ export function testCommand( export function getEditOperation(model: ITextModel, command: ICommand): IIdentifiedSingleEditOperation[] { let operations: IIdentifiedSingleEditOperation[] = []; let editOperationBuilder: IEditOperationBuilder = { - addEditOperation: (range: Range, text: string, forceMoveMarkers: boolean = false) => { + addEditOperation: (range: IRange, text: string, forceMoveMarkers: boolean = false) => { operations.push({ range: range, text: text, @@ -58,7 +58,7 @@ export function getEditOperation(model: ITextModel, command: ICommand): IIdentif }); }, - addTrackedEditOperation: (range: Range, text: string, forceMoveMarkers: boolean = false) => { + addTrackedEditOperation: (range: IRange, text: string, forceMoveMarkers: boolean = false) => { operations.push({ range: range, text: text, @@ -67,7 +67,7 @@ export function getEditOperation(model: ITextModel, command: ICommand): IIdentif }, - trackSelection: (selection: Selection) => { + trackSelection: (selection: ISelection) => { return ''; } }; diff --git a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index b0b0762997c..cde86790141 100644 --- a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts +++ b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -78,7 +78,7 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 2] = background.b; imageData.data[4 * i + 3] = 255; } - renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 2, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 2, false, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { @@ -108,7 +108,7 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 3] = 255; } - renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 1, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 1, false, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { diff --git a/src/vs/editor/test/common/editorTestUtils.ts b/src/vs/editor/test/common/editorTestUtils.ts index 3116fa31171..8298cf24b81 100644 --- a/src/vs/editor/test/common/editorTestUtils.ts +++ b/src/vs/editor/test/common/editorTestUtils.ts @@ -7,9 +7,12 @@ import { URI } from 'vs/base/common/uri'; import { DefaultEndOfLine, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageIdentifier } from 'vs/editor/common/modes'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; export function withEditorModel(text: string[], callback: (model: TextModel) => void): void { - let model = TextModel.createFromString(text.join('\n')); + let model = createTextModel(text.join('\n')); callback(model); model.dispose(); } @@ -36,5 +39,8 @@ export function createTextModel(text: string, _options: IRelaxedTextModelCreatio isForSimpleWidget: (typeof _options.isForSimpleWidget === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.isForSimpleWidget : _options.isForSimpleWidget), largeFileOptimizations: (typeof _options.largeFileOptimizations === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.largeFileOptimizations : _options.largeFileOptimizations), }; - return TextModel.createFromString(text, options, languageIdentifier, uri); + const dialogService = new TestDialogService(); + const notificationService = new TestNotificationService(); + const undoRedoService = new UndoRedoService(dialogService, notificationService); + return new TextModel(text, options, languageIdentifier, uri, undoRedoService); } 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 205da857f2b..67c8a119e30 100644 --- a/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -10,9 +10,10 @@ import { MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { assertSyncedModels, testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/editableTextModelTestUtils'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; function createEditableTextModelFromString(text: string): TextModel { - return new TextModel(text, TextModel.DEFAULT_CREATION_OPTIONS, null); + return createTextModel(text, TextModel.DEFAULT_CREATION_OPTIONS, null); } suite('EditorModel - EditableTextModel.applyEdits updates mightContainRTL', () => { @@ -1103,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 02fba238d80..9531a8bb1af 100644 --- a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts +++ b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts @@ -9,6 +9,7 @@ import { EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation import { MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; export function testApplyEditsWithSyncedModels(original: string[], edits: IIdentifiedSingleEditOperation[], expected: string[], inputEditsAreInvalid: boolean = false): void { let originalStr = original.join('\n'); @@ -16,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); @@ -24,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); @@ -35,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 @@ -88,7 +89,7 @@ function assertLineMapping(model: TextModel, msg: string): void { export function assertSyncedModels(text: string, callback: (model: TextModel, assertMirrorModels: () => void) => void, setup: ((model: TextModel) => void) | null = null): void { - let model = new TextModel(text, TextModel.DEFAULT_CREATION_OPTIONS, null); + let model = createTextModel(text, TextModel.DEFAULT_CREATION_OPTIONS, null); model.setEOL(EndOfLineSequence.LF); assertLineMapping(model, 'model'); 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/linesTextBuffer/textBufferAutoTestUtils.ts b/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts index c483bb64f81..49841dabc05 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts @@ -5,7 +5,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { Range } from 'vs/editor/common/core/range'; -import { DefaultEndOfLine, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferBuilder } from 'vs/editor/common/model'; +import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; export function getRandomInt(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; @@ -31,7 +31,7 @@ export function getRandomString(minLength: number, maxLength: number): string { return r; } -export function generateRandomEdits(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] { +export function generateRandomEdits(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { let newLines = chunk.split(/\r\n|\r|\n/); @@ -43,7 +43,7 @@ export function generateRandomEdits(chunks: string[], editCnt: number): IIdentif } } - let ops: IIdentifiedSingleEditOperation[] = []; + let ops: ValidAnnotatedEditOperation[] = []; for (let i = 0; i < editCnt; i++) { let line = getRandomInt(1, lines.length); @@ -54,17 +54,14 @@ export function generateRandomEdits(chunks: string[], editCnt: number): IIdentif text = getRandomString(5, 10); } - ops.push({ - text: text, - range: new Range(line, startColumn, line, endColumn) - }); + ops.push(new ValidAnnotatedEditOperation(null, new Range(line, startColumn, line, endColumn), text, false, false, false)); lines[line - 1] = lines[line - 1].substring(0, startColumn - 1) + text + lines[line - 1].substring(endColumn - 1); } return ops; } -export function generateSequentialInserts(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] { +export function generateSequentialInserts(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { let newLines = chunk.split(/\r\n|\r|\n/); @@ -76,7 +73,7 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): II } } - let ops: IIdentifiedSingleEditOperation[] = []; + let ops: ValidAnnotatedEditOperation[] = []; for (let i = 0; i < editCnt; i++) { let line = lines.length; @@ -90,16 +87,13 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): II lines[line - 1] += text; } - ops.push({ - text: text, - range: new Range(line, column, line, column) - }); + ops.push(new ValidAnnotatedEditOperation(null, new Range(line, column, line, column), text, false, false, false)); } return ops; } -export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): IIdentifiedSingleEditOperation[] { +export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { let newLines = chunk.split(/\r\n|\r|\n/); @@ -111,7 +105,7 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search } } - let ops: IIdentifiedSingleEditOperation[] = []; + let ops: ValidAnnotatedEditOperation[] = []; let chunkSize = Math.max(1, Math.floor(lines.length / editCnt)); let chunkCnt = Math.floor(lines.length / chunkSize); let replaceString = getRandomString(replaceStringLen, replaceStringLen); @@ -125,10 +119,7 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search let startColumn = getRandomInt(1, maxColumn); let endColumn = Math.min(maxColumn, startColumn + searchStringLen); - ops.push({ - text: replaceString, - range: new Range(line, startColumn, line, endColumn) - }); + ops.push(new ValidAnnotatedEditOperation(null, new Range(line, startColumn, line, endColumn), replaceString, false, false, false)); previousChunksLength = endLine; } @@ -166,4 +157,4 @@ export function generateRandomChunkWithLF(minLength: number, maxLength: number): } } return r; -} \ No newline at end of file +} diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index be29d6a9657..139b169bf1d 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -9,6 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageIdentifier, MetadataConsts } from 'vs/editor/common/modes'; import { ViewLineToken, ViewLineTokenFactory } from 'vs/editor/test/common/core/viewLineToken'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; interface ILineEdit { startColumn: number; @@ -106,7 +107,7 @@ suite('ModelLinesTokens', () => { function testApplyEdits(initial: IBufferLineState[], edits: IEdit[], expected: IBufferLineState[]): void { const initialText = initial.map(el => el.text).join('\n'); - const model = new TextModel(initialText, TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); + const model = createTextModel(initialText, TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); for (let lineIndex = 0; lineIndex < initial.length; lineIndex++) { const lineTokens = initial[lineIndex].tokens; const lineTextLength = model.getLineMaxColumn(lineIndex + 1) - 1; @@ -442,7 +443,7 @@ suite('ModelLinesTokens', () => { } test('insertion on empty line', () => { - const model = new TextModel('some text', TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); + const model = createTextModel('some text', TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); const tokens = TestToken.toTokens([new TestToken(0, 1)]); LineTokens.convertToEndOffset(tokens, model.getLineMaxColumn(1) - 1); model.setLineTokens(1, tokens); diff --git a/src/vs/editor/test/common/model/model.modes.test.ts b/src/vs/editor/test/common/model/model.modes.test.ts index 559a6f89f07..ce313b77a70 100644 --- a/src/vs/editor/test/common/model/model.modes.test.ts +++ b/src/vs/editor/test/common/model/model.modes.test.ts @@ -12,6 +12,7 @@ import { TokenizationResult2 } from 'vs/editor/common/core/token'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; // --------- utils @@ -46,7 +47,7 @@ suite('Editor Model - Model Modes 1', () => { const LANGUAGE_ID = 'modelModeTest1'; calledFor = []; languageRegistration = modes.TokenizationRegistry.register(LANGUAGE_ID, tokenizationSupport); - thisModel = TextModel.createFromString(TEXT, undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); + thisModel = createTextModel(TEXT, undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); }); teardown(() => { @@ -199,7 +200,7 @@ suite('Editor Model - Model Modes 2', () => { 'Line5'; const LANGUAGE_ID = 'modelModeTest2'; languageRegistration = modes.TokenizationRegistry.register(LANGUAGE_ID, tokenizationSupport); - thisModel = TextModel.createFromString(TEXT, undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); + thisModel = createTextModel(TEXT, undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); }); teardown(() => { diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 7fb877d7fd8..ed6787bc528 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -15,6 +15,7 @@ import { IState, LanguageIdentifier, MetadataConsts, TokenizationRegistry } from import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; // --------- utils @@ -35,7 +36,7 @@ suite('Editor Model - Model', () => { LINE3 + '\n' + LINE4 + '\r\n' + LINE5; - thisModel = TextModel.createFromString(text); + thisModel = createTextModel(text); }); teardown(() => { @@ -329,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)); @@ -349,7 +350,7 @@ suite('Editor Model - Model Line Separators', () => { LINE3 + '\u2028' + LINE4 + '\r\n' + LINE5; - thisModel = TextModel.createFromString(text); + thisModel = createTextModel(text); }); teardown(() => { @@ -365,7 +366,7 @@ suite('Editor Model - Model Line Separators', () => { }); test('Bug 13333:Model should line break on lonely CR too', () => { - let model = TextModel.createFromString('Hello\rWorld!\r\nAnother line'); + let model = createTextModel('Hello\rWorld!\r\nAnother line'); assert.equal(model.getLineCount(), 3); assert.equal(model.getValue(), 'Hello\r\nWorld!\r\nAnother line'); model.dispose(); @@ -430,7 +431,7 @@ suite('Editor Model - Words', () => { test('Get word at position', () => { const text = ['This text has some words. ']; - const thisModel = TextModel.createFromString(text.join('\n')); + const thisModel = createTextModel(text.join('\n')); disposables.push(thisModel); assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: 'This', startColumn: 1, endColumn: 5 }); @@ -451,7 +452,7 @@ suite('Editor Model - Words', () => { const innerMode = new InnerMode(); disposables.push(outerMode, innerMode); - const model = TextModel.createFromString('abab', undefined, outerMode.getLanguageIdentifier()); + const model = createTextModel('abab', undefined, outerMode.getLanguageIdentifier()); disposables.push(model); assert.deepEqual(model.getWordAtPosition(new Position(1, 1)), { word: 'ab', startColumn: 1, endColumn: 3 }); @@ -476,7 +477,7 @@ suite('Editor Model - Words', () => { }; disposables.push(mode); - const thisModel = TextModel.createFromString('.🐷-a-b', undefined, MODE_ID); + const thisModel = createTextModel('.🐷-a-b', undefined, MODE_ID); disposables.push(thisModel); assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: '.', startColumn: 1, endColumn: 2 }); diff --git a/src/vs/editor/test/common/model/modelDecorations.test.ts b/src/vs/editor/test/common/model/modelDecorations.test.ts index afe58fd37d7..4f7690f1111 100644 --- a/src/vs/editor/test/common/model/modelDecorations.test.ts +++ b/src/vs/editor/test/common/model/modelDecorations.test.ts @@ -9,6 +9,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; // --------- utils @@ -92,7 +93,7 @@ suite('Editor Model - Model Decorations', () => { LINE3 + '\n' + LINE4 + '\r\n' + LINE5; - thisModel = TextModel.createFromString(text); + thisModel = createTextModel(text); }); teardown(() => { @@ -400,7 +401,7 @@ suite('Editor Model - Model Decorations', () => { }); test('removeAllDecorationsWithOwnerId can be called after model dispose', () => { - let model = TextModel.createFromString('asd'); + let model = createTextModel('asd'); model.dispose(); model.removeAllDecorationsWithOwnerId(1); }); @@ -415,7 +416,7 @@ suite('Editor Model - Model Decorations', () => { suite('Decorations and editing', () => { function _runTest(decRange: Range, stickiness: TrackedRangeStickiness, editRange: Range, editText: string, editForceMoveMarkers: boolean, expectedDecRange: Range, msg: string): void { - let model = TextModel.createFromString([ + let model = createTextModel([ 'My First Line', 'My Second Line', 'Third Line' @@ -1148,7 +1149,7 @@ suite('deltaDecorations', () => { function testDeltaDecorations(text: string[], decorations: ILightWeightDecoration[], newDecorations: ILightWeightDecoration[]): void { - let model = TextModel.createFromString(text.join('\n')); + let model = createTextModel(text.join('\n')); // Add initial decorations & assert they are added let initialIds = model.deltaDecorations([], decorations.map(toModelDeltaDecoration)); @@ -1177,7 +1178,7 @@ suite('deltaDecorations', () => { } test('result respects input', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'Hello world,', 'How are you?' ].join('\n')); @@ -1265,7 +1266,7 @@ suite('deltaDecorations', () => { test('issue #4317: editor.setDecorations doesn\'t update the hover message', () => { - let model = TextModel.createFromString('Hello world!'); + let model = createTextModel('Hello world!'); let ids = model.deltaDecorations([], [{ range: { @@ -1299,7 +1300,7 @@ suite('deltaDecorations', () => { }); test('model doesn\'t get confused with individual tracked ranges', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'Hello world,', 'How are you?' ].join('\n')); @@ -1340,7 +1341,7 @@ suite('deltaDecorations', () => { }); test('issue #16922: Clicking on link doesn\'t seem to do anything', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'Hello world,', 'How are you?', 'Fine.', @@ -1371,7 +1372,7 @@ suite('deltaDecorations', () => { test('issue #41492: URL highlighting persists after pasting over url', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'My First Line' ].join('\n')); diff --git a/src/vs/editor/test/common/model/modelEditOperation.test.ts b/src/vs/editor/test/common/model/modelEditOperation.test.ts index 4340d6bbbeb..058c5d3d56a 100644 --- a/src/vs/editor/test/common/model/modelEditOperation.test.ts +++ b/src/vs/editor/test/common/model/modelEditOperation.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Editor Model - Model Edit Operation', () => { const LINE1 = 'My First Line'; @@ -23,7 +24,7 @@ suite('Editor Model - Model Edit Operation', () => { LINE3 + '\n' + LINE4 + '\r\n' + LINE5; - model = TextModel.createFromString(text); + model = createTextModel(text); }); teardown(() => { @@ -49,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); @@ -70,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/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts index e24401abda1..ddec1faccd1 100644 --- a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts +++ b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts @@ -12,7 +12,7 @@ import { PieceTreeBase } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceT import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { NodeColor, SENTINEL, TreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n'; @@ -1761,7 +1761,7 @@ function getValueInSnapshot(snapshot: ITextSnapshot) { } suite('snapshot', () => { test('bug #45564, piece tree pieces should be immutable', () => { - const model = TextModel.createFromString('\n'); + const model = createTextModel('\n'); model.applyEdits([ { range: new Range(2, 1, 2, 1), @@ -1789,7 +1789,7 @@ suite('snapshot', () => { }); test('immutable snapshot 1', () => { - const model = TextModel.createFromString('abc\ndef'); + const model = createTextModel('abc\ndef'); const snapshot = model.createSnapshot(); model.applyEdits([ { @@ -1809,7 +1809,7 @@ suite('snapshot', () => { }); test('immutable snapshot 2', () => { - const model = TextModel.createFromString('abc\ndef'); + const model = createTextModel('abc\ndef'); const snapshot = model.createSnapshot(); model.applyEdits([ { @@ -1829,7 +1829,7 @@ suite('snapshot', () => { }); test('immutable snapshot 3', () => { - const model = TextModel.createFromString('abc\ndef'); + const model = createTextModel('abc\ndef'); model.applyEdits([ { range: new Range(2, 4, 2, 4), @@ -1896,4 +1896,4 @@ suite('chunk based search', () => { assert.equal(ret.length, 1); assert.deepEqual(ret[0].range, new Range(2, 2, 2, 3)); }); -}); \ No newline at end of file +}); 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/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index bf4ee50506c..2da334170d2 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -163,7 +163,7 @@ suite('Editor Model - TextModel', () => { test('getValueLengthInRange', () => { - let m = TextModel.createFromString('My First Line\r\nMy Second Line\r\nMy Third Line'); + let m = createTextModel('My First Line\r\nMy Second Line\r\nMy Third Line'); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); assert.equal(m.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); @@ -176,7 +176,7 @@ suite('Editor Model - TextModel', () => { assert.equal(m.getValueLengthInRange(new Range(1, 2, 3, 1000)), 'y First Line\r\nMy Second Line\r\nMy Third Line'.length); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1000, 1000)), 'My First Line\r\nMy Second Line\r\nMy Third Line'.length); - m = TextModel.createFromString('My First Line\nMy Second Line\nMy Third Line'); + m = createTextModel('My First Line\nMy Second Line\nMy Third Line'); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); assert.equal(m.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); @@ -662,7 +662,7 @@ suite('Editor Model - TextModel', () => { test('validatePosition', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.validatePosition(new Position(0, 0)), new Position(1, 1)); assert.deepEqual(m.validatePosition(new Position(0, 1)), new Position(1, 1)); @@ -691,7 +691,7 @@ suite('Editor Model - TextModel', () => { test('validatePosition around high-low surrogate pairs 1', () => { - let m = TextModel.createFromString('a📚b'); + let m = createTextModel('a📚b'); assert.deepEqual(m.validatePosition(new Position(0, 0)), new Position(1, 1)); assert.deepEqual(m.validatePosition(new Position(0, 1)), new Position(1, 1)); @@ -718,7 +718,7 @@ suite('Editor Model - TextModel', () => { test('validatePosition around high-low surrogate pairs 2', () => { - let m = TextModel.createFromString('a📚📚b'); + let m = createTextModel('a📚📚b'); assert.deepEqual(m.validatePosition(new Position(1, 1)), new Position(1, 1)); assert.deepEqual(m.validatePosition(new Position(1, 2)), new Position(1, 2)); @@ -732,7 +732,7 @@ suite('Editor Model - TextModel', () => { test('validatePosition handle NaN.', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.validatePosition(new Position(NaN, 1)), new Position(1, 1)); assert.deepEqual(m.validatePosition(new Position(1, NaN)), new Position(1, 1)); @@ -743,7 +743,7 @@ suite('Editor Model - TextModel', () => { }); test('issue #71480: validatePosition handle floats', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.validatePosition(new Position(0.2, 1)), new Position(1, 1), 'a'); assert.deepEqual(m.validatePosition(new Position(1.2, 1)), new Position(1, 1), 'b'); @@ -756,7 +756,7 @@ suite('Editor Model - TextModel', () => { }); test('issue #71480: validateRange handle floats', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.validateRange(new Range(0.2, 1.5, 0.8, 2.5)), new Range(1, 1, 1, 1)); assert.deepEqual(m.validateRange(new Range(1.2, 1.7, 1.8, 2.2)), new Range(1, 1, 1, 2)); @@ -764,7 +764,7 @@ suite('Editor Model - TextModel', () => { test('validateRange around high-low surrogate pairs 1', () => { - let m = TextModel.createFromString('a📚b'); + let m = createTextModel('a📚b'); assert.deepEqual(m.validateRange(new Range(0, 0, 0, 1)), new Range(1, 1, 1, 1)); assert.deepEqual(m.validateRange(new Range(0, 0, 0, 7)), new Range(1, 1, 1, 1)); @@ -792,7 +792,7 @@ suite('Editor Model - TextModel', () => { test('validateRange around high-low surrogate pairs 2', () => { - let m = TextModel.createFromString('a📚📚b'); + let m = createTextModel('a📚📚b'); assert.deepEqual(m.validateRange(new Range(0, 0, 0, 1)), new Range(1, 1, 1, 1)); assert.deepEqual(m.validateRange(new Range(0, 0, 0, 7)), new Range(1, 1, 1, 1)); @@ -835,7 +835,7 @@ suite('Editor Model - TextModel', () => { test('modifyPosition', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.modifyPosition(new Position(1, 1), 0), new Position(1, 1)); assert.deepEqual(m.modifyPosition(new Position(0, 0), 0), new Position(1, 1)); assert.deepEqual(m.modifyPosition(new Position(30, 1), 0), new Position(2, 9)); @@ -913,7 +913,7 @@ suite('Editor Model - TextModel', () => { }); test('getLineFirstNonWhitespaceColumn', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'asd', ' asd', '\tasd', @@ -943,7 +943,7 @@ suite('Editor Model - TextModel', () => { }); test('getLineLastNonWhitespaceColumn', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'asd', 'asd ', 'asd\t', @@ -973,7 +973,7 @@ suite('Editor Model - TextModel', () => { }); test('#50471. getValueInRange with invalid range', () => { - let m = TextModel.createFromString('My First Line\r\nMy Second Line\r\nMy Third Line'); + let m = createTextModel('My First Line\r\nMy Second Line\r\nMy Third Line'); assert.equal(m.getValueInRange(new Range(1, NaN, 1, 3)), 'My'); assert.equal(m.getValueInRange(new Range(NaN, NaN, NaN, NaN)), ''); }); @@ -982,24 +982,24 @@ suite('Editor Model - TextModel', () => { suite('TextModel.mightContainRTL', () => { test('nope', () => { - let model = TextModel.createFromString('hello world!'); + let model = createTextModel('hello world!'); assert.equal(model.mightContainRTL(), false); }); test('yes', () => { - let model = TextModel.createFromString('Hello,\nזוהי עובדה מבוססת שדעתו'); + let model = createTextModel('Hello,\nזוהי עובדה מבוססת שדעתו'); assert.equal(model.mightContainRTL(), true); }); test('setValue resets 1', () => { - let model = TextModel.createFromString('hello world!'); + let model = createTextModel('hello world!'); assert.equal(model.mightContainRTL(), false); model.setValue('Hello,\nזוהי עובדה מבוססת שדעתו'); assert.equal(model.mightContainRTL(), true); }); test('setValue resets 2', () => { - let model = TextModel.createFromString('Hello,\nهناك حقيقة مثبتة منذ زمن طويل'); + let model = createTextModel('Hello,\nهناك حقيقة مثبتة منذ زمن طويل'); assert.equal(model.mightContainRTL(), true); model.setValue('hello world!'); assert.equal(model.mightContainRTL(), false); @@ -1010,14 +1010,14 @@ suite('TextModel.mightContainRTL', () => { suite('TextModel.createSnapshot', () => { test('empty file', () => { - let model = TextModel.createFromString(''); + let model = createTextModel(''); let snapshot = model.createSnapshot(); assert.equal(snapshot.read(), null); model.dispose(); }); test('file with BOM', () => { - let model = TextModel.createFromString(UTF8_BOM_CHARACTER + 'Hello'); + let model = createTextModel(UTF8_BOM_CHARACTER + 'Hello'); assert.equal(model.getLineContent(1), 'Hello'); let snapshot = model.createSnapshot(true); assert.equal(snapshot.read(), UTF8_BOM_CHARACTER + 'Hello'); @@ -1026,7 +1026,7 @@ suite('TextModel.createSnapshot', () => { }); test('regular file', () => { - let model = TextModel.createFromString('My First Line\n\t\tMy Second Line\n Third Line\n\n1'); + let model = createTextModel('My First Line\n\t\tMy Second Line\n Third Line\n\n1'); let snapshot = model.createSnapshot(); assert.equal(snapshot.read(), 'My First Line\n\t\tMy Second Line\n Third Line\n\n1'); assert.equal(snapshot.read(), null); @@ -1040,7 +1040,7 @@ suite('TextModel.createSnapshot', () => { } const text = lines.join('\n'); - let model = TextModel.createFromString(text); + let model = createTextModel(text); let snapshot = model.createSnapshot(); let actual = ''; diff --git a/src/vs/editor/test/common/model/textModelSearch.test.ts b/src/vs/editor/test/common/model/textModelSearch.test.ts index 2a55d718b50..e5af37e781b 100644 --- a/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -11,6 +11,7 @@ import { EndOfLineSequence, FindMatch } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { SearchData, SearchParams, TextModelSearch, isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; // --------- Find suite('TextModelSearch', () => { @@ -51,12 +52,12 @@ suite('TextModelSearch', () => { let expectedMatches = expectedRanges.map(entry => new FindMatch(entry, null)); let searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators); - let model = TextModel.createFromString(text); + let model = createTextModel(text); _assertFindMatches(model, searchParams, expectedMatches); model.dispose(); - let model2 = TextModel.createFromString(text); + let model2 = createTextModel(text); model2.setEOL(EndOfLineSequence.CRLF); _assertFindMatches(model2, searchParams, expectedMatches); model2.dispose(); @@ -380,7 +381,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch without regex', () => { - let model = TextModel.createFromString('line line one\nline two\nthree'); + let model = createTextModel('line line one\nline two\nthree'); let searchParams = new SearchParams('line', false, false, null); @@ -403,7 +404,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with beginning boundary regex', () => { - let model = TextModel.createFromString('line one\nline two\nthree'); + let model = createTextModel('line one\nline two\nthree'); let searchParams = new SearchParams('^line', true, false, null); @@ -423,7 +424,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with beginning boundary regex and line has repetitive beginnings', () => { - let model = TextModel.createFromString('line line one\nline two\nthree'); + let model = createTextModel('line line one\nline two\nthree'); let searchParams = new SearchParams('^line', true, false, null); @@ -443,7 +444,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with beginning boundary multiline regex and line has repetitive beginnings', () => { - let model = TextModel.createFromString('line line one\nline two\nline three\nline four'); + let model = createTextModel('line line one\nline two\nline three\nline four'); let searchParams = new SearchParams('^line.*\\nline', true, false, null); @@ -460,7 +461,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with ending boundary regex', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('line$', true, false, null); @@ -480,7 +481,7 @@ suite('TextModelSearch', () => { }); test('findMatches with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)', true, false, null); @@ -495,7 +496,7 @@ suite('TextModelSearch', () => { }); test('findMatches multiline with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)\\n', true, false, null); @@ -509,7 +510,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)', true, false, null); @@ -520,7 +521,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch multiline with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)\\n', true, false, null); @@ -531,7 +532,7 @@ suite('TextModelSearch', () => { }); test('findPreviousMatch with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)', true, false, null); @@ -542,7 +543,7 @@ suite('TextModelSearch', () => { }); test('findPreviousMatch multiline with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)\\n', true, false, null); @@ -553,7 +554,7 @@ suite('TextModelSearch', () => { }); test('\\n matches \\r\\n', () => { - let model = TextModel.createFromString('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); + let model = createTextModel('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); assert.equal(model.getEOL(), '\r\n'); @@ -576,7 +577,7 @@ suite('TextModelSearch', () => { }); test('\\r can never be found', () => { - let model = TextModel.createFromString('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); + let model = createTextModel('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); assert.equal(model.getEOL(), '\r\n'); @@ -763,7 +764,7 @@ suite('TextModelSearch', () => { }); test('issue #74715. \\d* finds empty string and stops searching.', () => { - let model = TextModel.createFromString('10.243.30.10'); + let model = createTextModel('10.243.30.10'); let searchParams = new SearchParams('\\d*', true, false, null); diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index b42c6dde00a..815c8a073e4 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -15,6 +15,7 @@ import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { ViewLineToken } from 'vs/editor/test/common/core/viewLineToken'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('TextModelWithTokens', () => { @@ -72,7 +73,7 @@ suite('TextModelWithTokens', () => { brackets: brackets }); - let model = new TextModel( + let model = createTextModel( contents.join('\n'), TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier @@ -178,7 +179,7 @@ suite('TextModelWithTokens - bracket matching', () => { let text = ')]}{[(' + '\n' + ')]}{[('; - let model = TextModel.createFromString(text, undefined, languageIdentifier); + let model = createTextModel(text, undefined, languageIdentifier); assertIsNotBracket(model, 1, 1); assertIsNotBracket(model, 1, 2); @@ -206,7 +207,7 @@ suite('TextModelWithTokens - bracket matching', () => { '}, bar: {hallo: [{' + '\n' + '}, {' + '\n' + '}]}}'; - let model = TextModel.createFromString(text, undefined, languageIdentifier); + let model = createTextModel(text, undefined, languageIdentifier); let brackets: [Position, Range, Range][] = [ [new Position(1, 11), new Range(1, 11, 1, 12), new Range(5, 4, 5, 5)], @@ -284,7 +285,7 @@ suite('TextModelWithTokens', () => { 'end;', ].join('\n'); - const model = TextModel.createFromString(text, undefined, languageIdentifier); + const model = createTextModel(text, undefined, languageIdentifier); // ... is not matched assertIsNotBracket(model, 10, 9); @@ -322,7 +323,7 @@ suite('TextModelWithTokens', () => { 'endrecord', ].join('\n'); - const model = TextModel.createFromString(text, undefined, languageIdentifier); + const model = createTextModel(text, undefined, languageIdentifier); // ... is matched assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 12), new Range(4, 1, 4, 10)]); @@ -388,7 +389,7 @@ suite('TextModelWithTokens', () => { ], }); - const model = TextModel.createFromString([ + const model = createTextModel([ 'function hello() {', ' console.log(`${100}`);', '}' @@ -459,7 +460,7 @@ suite('TextModelWithTokens regression tests', () => { let registration1 = TokenizationRegistry.register(LANG_ID1, tokenizationSupport); let registration2 = TokenizationRegistry.register(LANG_ID2, tokenizationSupport); - let model = TextModel.createFromString('A model with\ntwo lines'); + let model = createTextModel('A model with\ntwo lines'); assertViewLineTokens(model, 1, true, [createViewLineToken(12, 1)]); assertViewLineTokens(model, 2, true, [createViewLineToken(9, 1)]); @@ -498,7 +499,7 @@ suite('TextModelWithTokens regression tests', () => { ] }); - let model = TextModel.createFromString([ + let model = createTextModel([ 'Imports System', 'Imports System.Collections.Generic', '', @@ -528,7 +529,7 @@ suite('TextModelWithTokens regression tests', () => { ] }); - let model = TextModel.createFromString([ + let model = createTextModel([ 'sequence "outer"', ' sequence "inner"', ' endsequence', @@ -561,7 +562,7 @@ suite('TextModelWithTokens regression tests', () => { let registration = TokenizationRegistry.register(outerMode.language, tokenizationSupport); - let model = TextModel.createFromString('A model with one line', undefined, outerMode); + let model = createTextModel('A model with one line', undefined, outerMode); model.forceTokenization(1); assert.equal(model.getLanguageIdAtPosition(1, 1), innerMode.id); @@ -574,7 +575,7 @@ suite('TextModelWithTokens regression tests', () => { suite('TextModel.getLineIndentGuide', () => { function assertIndentGuides(lines: [number, number, number, number, string][], tabSize: number): void { let text = lines.map(l => l[4]).join('\n'); - let model = TextModel.createFromString(text); + let model = createTextModel(text); model.updateOptions({ tabSize: tabSize }); let actualIndents = model.getLinesIndentGuides(1, model.getLineCount()); @@ -747,7 +748,7 @@ suite('TextModel.getLineIndentGuide', () => { }); test('issue #49173', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'class A {', ' public m1(): void {', ' }', diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index eb81aa1570b..ff653c2c1d7 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -4,11 +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', () => { @@ -96,8 +98,8 @@ suite('TokensStore', () => { function testTokensAdjustment(rawInitialState: string[], edits: IIdentifiedSingleEditOperation[], rawFinalState: string[]) { const initialState = parseTokensState(rawInitialState); - const model = TextModel.createFromString(initialState.text); - model.setSemanticTokens([initialState.tokens]); + const model = createTextModel(initialState.text); + model.setSemanticTokens([initialState.tokens], true); model.applyEdits(edits); @@ -168,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 b6d04d367a2..f9a53739bdb 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -9,15 +9,20 @@ 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 { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { createTextBuffer } from 'vs/editor/common/model/textModel'; +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'; 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 { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; const GENERATE_TESTS = false; @@ -29,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()); + const dialogService = new TestDialogService(); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService); }); teardown(() => { @@ -48,7 +54,7 @@ suite('ModelService', () => { test('_computeEdits no change', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'This is line one', //16 'and this is line number two', //27 @@ -74,7 +80,7 @@ suite('ModelService', () => { test('_computeEdits first line changed', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'This is line one', //16 'and this is line number two', //27 @@ -102,7 +108,7 @@ suite('ModelService', () => { test('_computeEdits EOL changed', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'This is line one', //16 'and this is line number two', //27 @@ -128,7 +134,7 @@ suite('ModelService', () => { test('_computeEdits EOL and other change 1', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'This is line one', //16 'and this is line number two', //27 @@ -164,7 +170,7 @@ suite('ModelService', () => { test('_computeEdits EOL and other change 2', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'package main', // 1 'func foo() {', // 2 @@ -303,10 +309,79 @@ 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 { - const model = TextModel.createFromString(lines1.join('\n')); + const model = createTextModel(lines1.join('\n')); const textBuffer = createTextBuffer(lines2.join('\n'), DefaultEndOfLine.LF); // compute required edits diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index cfb8396fe37..f9e64360021 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -17,6 +17,7 @@ interface IEditorLayoutProviderOpts { readonly showLineNumbers: boolean; readonly lineNumbersMinChars: number; readonly lineNumbersDigitCount: number; + maxLineNumber?: number; readonly lineDecorationsWidth: number; @@ -32,6 +33,7 @@ interface IEditorLayoutProviderOpts { readonly minimapSide: 'left' | 'right'; readonly minimapRenderCharacters: boolean; readonly minimapMaxColumn: number; + minimapSize?: 'proportional' | 'fill' | 'fit'; readonly pixelRatio: number; } @@ -45,6 +47,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { options._write(EditorOption.folding, false); const minimapOptions: EditorMinimapOptions = { enabled: input.minimap, + size: input.minimapSize || 'proportional', side: input.minimapSide, renderCharacters: input.minimapRenderCharacters, maxColumn: input.minimapMaxColumn, @@ -77,6 +80,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { outerWidth: input.outerWidth, outerHeight: input.outerHeight, lineHeight: input.lineHeight, + viewLineCount: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1, lineNumbersDigitCount: input.lineNumbersDigitCount, typicalHalfwidthCharacterWidth: input.typicalHalfwidthCharacterWidth, maxDigitWidth: input.maxDigitWidth, @@ -125,6 +129,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 800, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 800, viewportColumn: 98, verticalScrollbarWidth: 0, @@ -179,6 +191,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 800, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 800, viewportColumn: 97, verticalScrollbarWidth: 11, @@ -233,6 +253,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 800, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 800, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -287,6 +315,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -341,6 +377,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 88, verticalScrollbarWidth: 0, @@ -395,6 +439,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 83, verticalScrollbarWidth: 0, @@ -449,6 +501,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 83, verticalScrollbarWidth: 0, @@ -503,6 +563,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 82, verticalScrollbarWidth: 0, @@ -557,6 +625,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 171, verticalScrollbarWidth: 0, @@ -611,6 +687,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.None, minimapLeft: 0, minimapWidth: 0, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 0, + minimapCanvasInnerHeight: 900, + minimapCanvasOuterWidth: 0, + minimapCanvasOuterHeight: 900, viewportColumn: 169, verticalScrollbarWidth: 0, @@ -665,6 +749,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 903, minimapWidth: 97, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 1, + minimapLineHeight: 2, + minimapCanvasInnerWidth: 97, + minimapCanvasInnerHeight: 800, + minimapCanvasOuterWidth: 97, + minimapCanvasOuterHeight: 800, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -719,6 +811,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 903, minimapWidth: 97, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 2, + minimapLineHeight: 4, + minimapCanvasInnerWidth: 194, + minimapCanvasInnerHeight: 1600, + minimapCanvasOuterWidth: 97, + minimapCanvasOuterHeight: 800, viewportColumn: 89, verticalScrollbarWidth: 0, @@ -773,6 +873,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 945, minimapWidth: 55, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 2, + minimapLineHeight: 4, + minimapCanvasInnerWidth: 220, + minimapCanvasInnerHeight: 3200, + minimapCanvasOuterWidth: 55, + minimapCanvasOuterHeight: 800, viewportColumn: 93, verticalScrollbarWidth: 0, @@ -827,6 +935,270 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 0, minimapWidth: 55, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 2, + minimapLineHeight: 4, + minimapCanvasInnerWidth: 220, + minimapCanvasInnerHeight: 3200, + minimapCanvasOuterWidth: 55, + minimapCanvasOuterHeight: 800, + viewportColumn: 93, + + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + + overviewRuler: { + top: 0, + width: 0, + height: 800, + right: 0 + } + }); + }); + + test('EditorLayoutProvider 11 - minimap mode cover without sampling', () => { + doTest({ + outerWidth: 1000, + outerHeight: 800, + showGlyphMargin: false, + lineHeight: 16, + showLineNumbers: false, + lineNumbersMinChars: 0, + lineNumbersDigitCount: 3, + maxLineNumber: 120, + lineDecorationsWidth: 10, + typicalHalfwidthCharacterWidth: 10, + maxDigitWidth: 10, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + scrollbarArrowSize: 0, + verticalScrollbarHasArrows: false, + minimap: true, + minimapSide: 'right', + minimapRenderCharacters: true, + minimapMaxColumn: 150, + minimapSize: 'fill', + pixelRatio: 2, + }, { + width: 1000, + height: 800, + + glyphMarginLeft: 0, + glyphMarginWidth: 0, + + lineNumbersLeft: 0, + lineNumbersWidth: 0, + + decorationsLeft: 0, + decorationsWidth: 10, + + contentLeft: 10, + contentWidth: 893, + + renderMinimap: RenderMinimap.Text, + minimapLeft: 903, + minimapWidth: 97, + minimapHeightIsEditorHeight: true, + minimapIsSampling: false, + minimapScale: 3, + minimapLineHeight: 13, + minimapCanvasInnerWidth: 291, + minimapCanvasInnerHeight: 1560, + minimapCanvasOuterWidth: 97, + minimapCanvasOuterHeight: 800, + viewportColumn: 89, + + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + + overviewRuler: { + top: 0, + width: 0, + height: 800, + right: 0 + } + }); + }); + + test('EditorLayoutProvider 12 - minimap mode cover with sampling', () => { + doTest({ + outerWidth: 1000, + outerHeight: 800, + showGlyphMargin: false, + lineHeight: 16, + showLineNumbers: false, + lineNumbersMinChars: 0, + lineNumbersDigitCount: 4, + maxLineNumber: 2500, + lineDecorationsWidth: 10, + typicalHalfwidthCharacterWidth: 10, + maxDigitWidth: 10, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + scrollbarArrowSize: 0, + verticalScrollbarHasArrows: false, + minimap: true, + minimapSide: 'right', + minimapRenderCharacters: true, + minimapMaxColumn: 150, + minimapSize: 'fill', + pixelRatio: 2, + }, { + width: 1000, + height: 800, + + glyphMarginLeft: 0, + glyphMarginWidth: 0, + + lineNumbersLeft: 0, + lineNumbersWidth: 0, + + decorationsLeft: 0, + decorationsWidth: 10, + + contentLeft: 10, + contentWidth: 935, + + renderMinimap: RenderMinimap.Text, + minimapLeft: 945, + minimapWidth: 55, + minimapHeightIsEditorHeight: true, + minimapIsSampling: true, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 110, + minimapCanvasInnerHeight: 1600, + minimapCanvasOuterWidth: 55, + minimapCanvasOuterHeight: 800, + viewportColumn: 93, + + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + + overviewRuler: { + top: 0, + width: 0, + height: 800, + right: 0 + } + }); + }); + + test('EditorLayoutProvider 13 - minimap mode contain without sampling', () => { + doTest({ + outerWidth: 1000, + outerHeight: 800, + showGlyphMargin: false, + lineHeight: 16, + showLineNumbers: false, + lineNumbersMinChars: 0, + lineNumbersDigitCount: 3, + maxLineNumber: 120, + lineDecorationsWidth: 10, + typicalHalfwidthCharacterWidth: 10, + maxDigitWidth: 10, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + scrollbarArrowSize: 0, + verticalScrollbarHasArrows: false, + minimap: true, + minimapSide: 'right', + minimapRenderCharacters: true, + minimapMaxColumn: 150, + minimapSize: 'fit', + pixelRatio: 2, + }, { + width: 1000, + height: 800, + + glyphMarginLeft: 0, + glyphMarginWidth: 0, + + lineNumbersLeft: 0, + lineNumbersWidth: 0, + + decorationsLeft: 0, + decorationsWidth: 10, + + contentLeft: 10, + contentWidth: 893, + + renderMinimap: RenderMinimap.Text, + minimapLeft: 903, + minimapWidth: 97, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 2, + minimapLineHeight: 4, + minimapCanvasInnerWidth: 194, + minimapCanvasInnerHeight: 1600, + minimapCanvasOuterWidth: 97, + minimapCanvasOuterHeight: 800, + viewportColumn: 89, + + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + + overviewRuler: { + top: 0, + width: 0, + height: 800, + right: 0 + } + }); + }); + + test('EditorLayoutProvider 14 - minimap mode contain with sampling', () => { + doTest({ + outerWidth: 1000, + outerHeight: 800, + showGlyphMargin: false, + lineHeight: 16, + showLineNumbers: false, + lineNumbersMinChars: 0, + lineNumbersDigitCount: 4, + maxLineNumber: 2500, + lineDecorationsWidth: 10, + typicalHalfwidthCharacterWidth: 10, + maxDigitWidth: 10, + verticalScrollbarWidth: 0, + horizontalScrollbarHeight: 0, + scrollbarArrowSize: 0, + verticalScrollbarHasArrows: false, + minimap: true, + minimapSide: 'right', + minimapRenderCharacters: true, + minimapMaxColumn: 150, + minimapSize: 'fit', + pixelRatio: 2, + }, { + width: 1000, + height: 800, + + glyphMarginLeft: 0, + glyphMarginWidth: 0, + + lineNumbersLeft: 0, + lineNumbersWidth: 0, + + decorationsLeft: 0, + decorationsWidth: 10, + + contentLeft: 10, + contentWidth: 935, + + renderMinimap: RenderMinimap.Text, + minimapLeft: 945, + minimapWidth: 55, + minimapHeightIsEditorHeight: true, + minimapIsSampling: true, + minimapScale: 1, + minimapLineHeight: 1, + minimapCanvasInnerWidth: 110, + minimapCanvasInnerHeight: 1600, + minimapCanvasOuterWidth: 55, + minimapCanvasOuterHeight: 800, viewportColumn: 93, verticalScrollbarWidth: 0, @@ -881,6 +1253,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { renderMinimap: RenderMinimap.Text, minimapLeft: 1096, minimapWidth: 91, + minimapHeightIsEditorHeight: false, + minimapIsSampling: false, + minimapScale: 2, + minimapLineHeight: 4, + minimapCanvasInnerWidth: 182, + minimapCanvasInnerHeight: 844, + minimapCanvasOuterWidth: 91, + minimapCanvasOuterHeight: 422, viewportColumn: 83, verticalScrollbarWidth: 14, diff --git a/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts b/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts index 053a3ef301a..8be7627bc72 100644 --- a/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts +++ b/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts @@ -18,9 +18,9 @@ suite('Editor ViewLayout - ViewLineParts', () => { ]); assert.deepEqual(result, [ - new DecorationSegment(0, 1, 'c1'), - new DecorationSegment(2, 2, 'c2 c1'), - new DecorationSegment(3, 9, 'c1'), + new DecorationSegment(0, 1, 'c1', 0), + new DecorationSegment(2, 2, 'c2 c1', 0), + new DecorationSegment(3, 9, 'c1', 0), ]); }); @@ -32,8 +32,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { ]); assert.deepEqual(result, [ - new DecorationSegment(14, 18, 'mtkw'), - new DecorationSegment(19, 19, 'mtkw inline-folded') + new DecorationSegment(14, 18, 'mtkw', 0), + new DecorationSegment(19, 19, 'mtkw inline-folded', 0) ]); }); @@ -66,24 +66,24 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(1, 2, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 0, 'c1'), - new DecorationSegment(2, 2, 'c2') + new DecorationSegment(0, 0, 'c1', 0), + new DecorationSegment(2, 2, 'c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 3, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1'), - new DecorationSegment(2, 2, 'c2') + new DecorationSegment(0, 1, 'c1', 0), + new DecorationSegment(2, 2, 'c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ new LineDecoration(1, 4, 'c1', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1'), - new DecorationSegment(2, 2, 'c1 c2') + new DecorationSegment(0, 1, 'c1', 0), + new DecorationSegment(2, 2, 'c1 c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ @@ -91,8 +91,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(1, 4, 'c1*', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1 c1*'), - new DecorationSegment(2, 2, 'c1 c1* c2') + new DecorationSegment(0, 1, 'c1 c1*', 0), + new DecorationSegment(2, 2, 'c1 c1* c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ @@ -101,8 +101,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(1, 4, 'c1**', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1 c1* c1**'), - new DecorationSegment(2, 2, 'c1 c1* c1** c2') + new DecorationSegment(0, 1, 'c1 c1* c1**', 0), + new DecorationSegment(2, 2, 'c1 c1* c1** c2', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ @@ -112,8 +112,8 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular), new LineDecoration(3, 4, 'c2*', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1 c1* c1**'), - new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*') + new DecorationSegment(0, 1, 'c1 c1* c1**', 0), + new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*', 0) ]); assert.deepEqual(LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ @@ -123,9 +123,9 @@ suite('Editor ViewLayout - ViewLineParts', () => { new LineDecoration(3, 4, 'c2', InlineDecorationType.Regular), new LineDecoration(3, 5, 'c2*', InlineDecorationType.Regular) ]), [ - new DecorationSegment(0, 1, 'c1 c1* c1**'), - new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*'), - new DecorationSegment(3, 3, 'c2*') + new DecorationSegment(0, 1, 'c1 c1* c1**', 0), + new DecorationSegment(2, 2, 'c1 c1* c1** c2 c2*', 0), + new DecorationSegment(3, 3, 'c2*', 0) ]); }); }); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index f52524badd0..7124aba04e5 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -388,6 +388,66 @@ suite('viewLineRenderer.renderLine', () => { assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]); }); + test('issue #91178: after decoration type shown before cursor', () => { + const lineText = '//just a comment'; + const lineParts = createViewLineTokens([ + createPart(16, 1) + ]); + const expectedOutput = [ + '//just\u00a0a\u00a0com', + '', + '', + 'ment', + ].join(''); + + const expectedCharacterMapping = new CharacterMapping(17, 4); + expectedCharacterMapping.setPartData(0, 0, 0, 0); + expectedCharacterMapping.setPartData(1, 0, 1, 0); + expectedCharacterMapping.setPartData(2, 0, 2, 0); + expectedCharacterMapping.setPartData(3, 0, 3, 0); + expectedCharacterMapping.setPartData(4, 0, 4, 0); + expectedCharacterMapping.setPartData(5, 0, 5, 0); + expectedCharacterMapping.setPartData(6, 0, 6, 0); + expectedCharacterMapping.setPartData(7, 0, 7, 0); + expectedCharacterMapping.setPartData(8, 0, 8, 0); + expectedCharacterMapping.setPartData(9, 0, 9, 0); + expectedCharacterMapping.setPartData(10, 0, 10, 0); + expectedCharacterMapping.setPartData(11, 0, 11, 0); + expectedCharacterMapping.setPartData(12, 2, 0, 12); + expectedCharacterMapping.setPartData(13, 3, 1, 12); + expectedCharacterMapping.setPartData(14, 3, 2, 12); + expectedCharacterMapping.setPartData(15, 3, 3, 12); + expectedCharacterMapping.setPartData(16, 3, 4, 12); + + const actual = renderViewLine(new RenderLineInput( + true, + false, + lineText, + false, + true, + false, + 0, + lineParts, + [ + new LineDecoration(13, 13, 'dec1', InlineDecorationType.After), + new LineDecoration(13, 13, 'dec2', InlineDecorationType.Before), + ], + 4, + 0, + 10, + 10, + 10, + -1, + 'none', + false, + false, + null + )); + + assert.equal(actual.html, '' + expectedOutput + ''); + assertCharacterMapping2(actual.characterMapping, expectedCharacterMapping); + }); + test('issue Microsoft/monaco-editor#280: Improved source code rendering for RTL languages', () => { let lineText = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";'; @@ -399,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( @@ -427,7 +487,7 @@ suite('viewLineRenderer.renderLine', () => { null )); - assert.equal(_actual.html, '' + expectedOutput + ''); + assert.equal(_actual.html, '' + expectedOutput + ''); assert.equal(_actual.containsRTL, true); }); @@ -616,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, @@ -639,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); }); @@ -693,6 +753,33 @@ suite('viewLineRenderer.renderLine', () => { assert.equal(_actual.html, '' + expectedOutput + ''); }); + interface ICharMappingData { + charOffset: number; + partIndex: number; + charIndex: number; + } + + function decodeCharacterMapping(source: CharacterMapping) { + const mapping: ICharMappingData[] = []; + for (let charOffset = 0; charOffset < source.length; charOffset++) { + const partData = source.charOffsetToPartData(charOffset); + const partIndex = CharacterMapping.getPartIndex(partData); + const charIndex = CharacterMapping.getCharIndex(partData); + mapping.push({ charOffset, partIndex, charIndex }); + } + const absoluteOffsets: number[] = []; + for (const absoluteOffset of source.getAbsoluteOffsets()) { + absoluteOffsets.push(absoluteOffset); + } + return { mapping, absoluteOffsets }; + } + + function assertCharacterMapping2(actual: CharacterMapping, expected: CharacterMapping): void { + const _actual = decodeCharacterMapping(actual); + const _expected = decodeCharacterMapping(expected); + assert.deepEqual(_actual, _expected); + } + function assertCharacterMapping(actual: CharacterMapping, expectedCharPartOffsets: number[][], expectedPartLengths: number[]): void { assertCharPartOffsets(actual, expectedCharPartOffsets); @@ -1774,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/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 393d6997976..2c5cca1cb71 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -18,6 +18,7 @@ import { LineBreakData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Editor ViewModel - SplitLinesCollection', () => { test('SplitLine', () => { @@ -96,7 +97,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters); - const model = TextModel.createFromString([ + const model = createTextModel([ 'int main() {', '\tprintf("Hello world!");', '}', @@ -347,7 +348,7 @@ suite('SplitLinesCollection', () => { }; const LANGUAGE_ID = 'modelModeTest1'; languageRegistration = modes.TokenizationRegistry.register(LANGUAGE_ID, tokenizationSupport); - model = TextModel.createFromString(_text.join('\n'), undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); + model = createTextModel(_text.join('\n'), undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); // force tokenization model.forceTokenization(model.getLineCount()); }); diff --git a/src/vs/editor/test/common/viewModel/testViewModel.ts b/src/vs/editor/test/common/viewModel/testViewModel.ts index b316d88b004..38f1fc586f1 100644 --- a/src/vs/editor/test/common/viewModel/testViewModel.ts +++ b/src/vs/editor/test/common/viewModel/testViewModel.ts @@ -8,12 +8,13 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void { const EDITOR_ID = 1; const configuration = new TestConfiguration(options); - const model = TextModel.createFromString(text.join('\n')); + const model = createTextModel(text.join('\n')); const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(configuration.options); const viewModel = new ViewModel(EDITOR_ID, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); 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/loader.js b/src/vs/loader.js index 80d0e52ceea..ce60a2a3312 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -923,7 +923,11 @@ var AMDLoader; var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest(); if (!hashData.equals(hashDataNow)) { moduleManager.getConfig().onError(new Error("FAILED TO VERIFY CACHED DATA, deleting stale '" + cachedDataPath + "' now, but a RESTART IS REQUIRED")); - _this._fs.unlink(cachedDataPath, function (err) { return moduleManager.getConfig().onError(err); }); + _this._fs.unlink(cachedDataPath, function (err) { + if (err) { + moduleManager.getConfig().onError(err); + } + }); } }, Math.ceil(5000 * (1 + Math.random()))); }; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index bee004f486d..99d3a926bea 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; @@ -638,10 +647,18 @@ declare namespace monaco { * Return the end position (which will be after or equal to the start position) */ getEndPosition(): Position; + /** + * Return the end position (which will be after or equal to the start position) + */ + static getEndPosition(range: IRange): Position; /** * Return the start position (which will be before or equal to the end position) */ getStartPosition(): Position; + /** + * Return the start position (which will be before or equal to the end position) + */ + static getStartPosition(range: IRange): Position; /** * Transform to a user presentable string representation. */ @@ -1098,6 +1115,11 @@ declare namespace monaco.editor { * Defaults to true. */ wordBasedSuggestions?: boolean; + /** + * Controls whether the semanticHighlighting is shown for the languages that support it. + * Defaults to true. + */ + 'semanticHighlighting.enabled'?: boolean; /** * Keep peek editors open even when double clicking their content or when hitting `Escape`. * Defaults to false. @@ -1108,6 +1130,13 @@ declare namespace monaco.editor { * Defaults to 20000. */ maxTokenizationLineLength?: number; + /** + * Theme to be used for rendering. + * The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black'. + * You can create custom themes via `monaco.editor.defineTheme`. + * To switch a theme, use `monaco.editor.setTheme` + */ + theme?: string; } /** @@ -1350,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. */ @@ -1501,7 +1534,7 @@ declare namespace monaco.editor { /** * The range to replace. This can be empty to emulate a simple insert. */ - range: Range; + range: IRange; /** * The text to replace with. This can be null to emulate a simple delete. */ @@ -1513,6 +1546,17 @@ declare namespace monaco.editor { forceMoveMarkers?: boolean; } + export interface IValidEditOperation { + /** + * The range to replace. This can be empty to emulate a simple insert. + */ + range: Range; + /** + * The text to replace with. This can be empty to emulate a simple delete. + */ + text: string; + } + /** * A callback that can compute the cursor state after applying a series of edit operations. */ @@ -1520,7 +1564,7 @@ declare namespace monaco.editor { /** * A callback that can compute the resulting cursors state after some edit operations have been executed. */ - (inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null; + (inverseEditOperations: IValidEditOperation[]): Selection[] | null; } export class TextModelResolvedOptions { @@ -1848,7 +1892,7 @@ declare namespace monaco.editor { * @param cursorStateComputer A callback that can compute the resulting cursors state after the edit operations have been executed. * @return The cursor state returned by the `cursorStateComputer`. */ - pushEditOperations(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; + pushEditOperations(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; /** * Change the end of line sequence. This is the preferred way of * changing the eol sequence. This will land on the undo stack. @@ -1858,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[]): IIdentifiedSingleEditOperation[]; + 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. @@ -1912,14 +1958,14 @@ declare namespace monaco.editor { * @param range The range to replace (delete). May be empty to represent a simple insert. * @param text The text to replace with. May be null to represent a simple delete. */ - addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; + addEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void; /** * Add a new edit operation (a replace operation). * The inverse edits will be accessible in `ICursorStateComputerData.getInverseEditOperations()` * @param range The range to replace (delete). May be empty to represent a simple insert. * @param text The text to replace with. May be null to represent a simple delete. */ - addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; + addTrackedEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void; /** * Track `selection` when applying edit operations. * A best effort will be made to not grow/expand the selection. @@ -1939,7 +1985,7 @@ declare namespace monaco.editor { /** * Get the inverse edit operations of the added edit operations. */ - getInverseEditOperations(): IIdentifiedSingleEditOperation[]; + getInverseEditOperations(): IValidEditOperation[]; /** * Get a previously tracked selection. * @param id The unique identifier returned by `trackSelection`. @@ -2271,6 +2317,11 @@ declare namespace monaco.editor { * optimized for viewing a code definition. */ revealRangeNearTop(range: IRange, scrollType?: ScrollType): void; + /** + * Scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, + * optimized for viewing a code definition. Only if it lies outside the viewport. + */ + revealRangeNearTopIfOutsideViewport(range: IRange, scrollType?: ScrollType): void; /** * Directly trigger a handler or an editor action. * @param source The source of the call. @@ -2390,6 +2441,8 @@ declare namespace monaco.editor { * An event describing that model decorations have changed. */ export interface IModelDecorationsChangedEvent { + readonly affectsMinimap: boolean; + readonly affectsOverviewRuler: boolean; } export interface IModelOptionsChangedEvent { @@ -2617,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. @@ -2806,6 +2864,11 @@ declare namespace monaco.editor { * Defaults to true. */ scrollPredominantAxis?: boolean; + /** + * Enable that the selection with the mouse and keys is doing column selection. + * Defaults to false. + */ + columnSelection?: boolean; /** * The modifier to be used to add multiple cursors with the mouse. * Defaults to 'alt' @@ -2983,6 +3046,11 @@ declare namespace monaco.editor { * Defaults to 'mouseover'. */ showFoldingControls?: 'always' | 'mouseover'; + /** + * Controls whether clicking on the empty content after a folded line will unfold the line. + * Defaults to false. + */ + unfoldOnClickAfterEndOfLine?: boolean; /** * Enable highlighting of matching brackets. * Defaults to 'always'. @@ -3013,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. */ @@ -3202,6 +3275,10 @@ declare namespace monaco.editor { */ autoFindInSelection?: 'never' | 'always' | 'multiline'; addExtraSpaceOnTop?: boolean; + /** + * Controls whether the search automatically restarts from the beginning (or the end) when no further matches can be found + */ + loop?: boolean; } export type EditorFindOptions = Readonly>; @@ -3330,6 +3407,14 @@ declare namespace monaco.editor { * The width of the minimap */ readonly minimapWidth: number; + readonly minimapHeightIsEditorHeight: boolean; + readonly minimapIsSampling: boolean; + readonly minimapScale: number; + readonly minimapLineHeight: number; + readonly minimapCanvasInnerWidth: number; + readonly minimapCanvasInnerHeight: number; + readonly minimapCanvasOuterWidth: number; + readonly minimapCanvasOuterHeight: number; /** * Minimap render type */ @@ -3379,6 +3464,11 @@ declare namespace monaco.editor { * Defaults to 'right'. */ side?: 'right' | 'left'; + /** + * Control the minimap rendering mode. + * Defaults to 'actual'. + */ + size?: 'proportional' | 'fill' | 'fit'; /** * Control the rendering of the minimap slider. * Defaults to 'mouseover'. @@ -3559,10 +3649,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. */ @@ -3683,14 +3769,27 @@ declare namespace monaco.editor { * Show typeParameter-suggestions. */ showTypeParameters?: boolean; + /** + * Show issue-suggestions. + */ + showIssues?: boolean; + /** + * Show user-suggestions. + */ + showUsers?: boolean; /** * Show snippet-suggestions. */ showSnippets?: boolean; /** - * Controls the visibility of the status bar at the bottom of the suggest widget. + * Status bar related settings. */ - hideStatusBar?: boolean; + statusBar?: { + /** + * Controls the visibility of the status bar at the bottom of the suggest widget. + */ + visible?: boolean; + }; } export type InternalSuggestOptions = Readonly>; @@ -3738,105 +3837,109 @@ declare namespace monaco.editor { autoSurround = 10, codeLens = 11, colorDecorators = 12, - comments = 13, - contextmenu = 14, - copyWithSyntaxHighlighting = 15, - cursorBlinking = 16, - cursorSmoothCaretAnimation = 17, - cursorStyle = 18, - cursorSurroundingLines = 19, - cursorSurroundingLinesStyle = 20, - cursorWidth = 21, - disableLayerHinting = 22, - disableMonospaceOptimizations = 23, - dragAndDrop = 24, - emptySelectionClipboard = 25, - extraEditorClassName = 26, - fastScrollSensitivity = 27, - find = 28, - fixedOverflowWidgets = 29, - folding = 30, - foldingStrategy = 31, - foldingHighlight = 32, - fontFamily = 33, - fontInfo = 34, - fontLigatures = 35, - fontSize = 36, - fontWeight = 37, - formatOnPaste = 38, - formatOnType = 39, - glyphMargin = 40, - gotoLocation = 41, - hideCursorInOverviewRuler = 42, - highlightActiveIndentGuide = 43, - hover = 44, - inDiffEditor = 45, - letterSpacing = 46, - lightbulb = 47, - lineDecorationsWidth = 48, - lineHeight = 49, - lineNumbers = 50, - lineNumbersMinChars = 51, - links = 52, - matchBrackets = 53, - minimap = 54, - mouseStyle = 55, - mouseWheelScrollSensitivity = 56, - mouseWheelZoom = 57, - multiCursorMergeOverlapping = 58, - multiCursorModifier = 59, - multiCursorPaste = 60, - occurrencesHighlight = 61, - overviewRulerBorder = 62, - overviewRulerLanes = 63, - padding = 64, - parameterHints = 65, - peekWidgetDefaultFocus = 66, - definitionLinkOpensInPeek = 67, - quickSuggestions = 68, - quickSuggestionsDelay = 69, - readOnly = 70, - renderControlCharacters = 71, - renderIndentGuides = 72, - renderFinalNewline = 73, - renderLineHighlight = 74, - renderValidationDecorations = 75, - renderWhitespace = 76, - revealHorizontalRightPadding = 77, - roundedSelection = 78, - rulers = 79, - scrollbar = 80, - scrollBeyondLastColumn = 81, - scrollBeyondLastLine = 82, - scrollPredominantAxis = 83, - selectionClipboard = 84, - selectionHighlight = 85, - selectOnLineNumbers = 86, - showFoldingControls = 87, - showUnused = 88, - snippetSuggestions = 89, - smoothScrolling = 90, - stopRenderingLineAfter = 91, - suggest = 92, - suggestFontSize = 93, - suggestLineHeight = 94, - suggestOnTriggerCharacters = 95, - suggestSelection = 96, - tabCompletion = 97, - useTabStops = 98, - wordSeparators = 99, - wordWrap = 100, - wordWrapBreakAfterCharacters = 101, - wordWrapBreakBeforeCharacters = 102, - wordWrapColumn = 103, - wordWrapMinified = 104, - wrappingIndent = 105, - wrappingStrategy = 106, - editorClassName = 107, - pixelRatio = 108, - tabFocusMode = 109, - layoutInfo = 110, - wrappingInfo = 111 + columnSelection = 13, + comments = 14, + contextmenu = 15, + copyWithSyntaxHighlighting = 16, + cursorBlinking = 17, + cursorSmoothCaretAnimation = 18, + cursorStyle = 19, + cursorSurroundingLines = 20, + cursorSurroundingLinesStyle = 21, + cursorWidth = 22, + disableLayerHinting = 23, + disableMonospaceOptimizations = 24, + dragAndDrop = 25, + emptySelectionClipboard = 26, + extraEditorClassName = 27, + fastScrollSensitivity = 28, + find = 29, + fixedOverflowWidgets = 30, + folding = 31, + foldingStrategy = 32, + foldingHighlight = 33, + unfoldOnClickAfterEndOfLine = 34, + fontFamily = 35, + fontInfo = 36, + fontLigatures = 37, + fontSize = 38, + fontWeight = 39, + formatOnPaste = 40, + formatOnType = 41, + glyphMargin = 42, + gotoLocation = 43, + hideCursorInOverviewRuler = 44, + highlightActiveIndentGuide = 45, + hover = 46, + inDiffEditor = 47, + letterSpacing = 48, + lightbulb = 49, + lineDecorationsWidth = 50, + lineHeight = 51, + lineNumbers = 52, + lineNumbersMinChars = 53, + links = 54, + matchBrackets = 55, + minimap = 56, + mouseStyle = 57, + mouseWheelScrollSensitivity = 58, + mouseWheelZoom = 59, + multiCursorMergeOverlapping = 60, + multiCursorModifier = 61, + multiCursorPaste = 62, + occurrencesHighlight = 63, + overviewRulerBorder = 64, + overviewRulerLanes = 65, + padding = 66, + parameterHints = 67, + peekWidgetDefaultFocus = 68, + definitionLinkOpensInPeek = 69, + quickSuggestions = 70, + quickSuggestionsDelay = 71, + readOnly = 72, + 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; @@ -3852,6 +3955,7 @@ declare namespace monaco.editor { autoSurround: IEditorOption; codeLens: IEditorOption; colorDecorators: IEditorOption; + columnSelection: IEditorOption; comments: IEditorOption; contextmenu: IEditorOption; copyWithSyntaxHighlighting: IEditorOption; @@ -3872,6 +3976,7 @@ declare namespace monaco.editor { folding: IEditorOption; foldingStrategy: IEditorOption; foldingHighlight: IEditorOption; + unfoldOnClickAfterEndOfLine: IEditorOption; fontFamily: IEditorOption; fontInfo: IEditorOption; fontLigatures2: IEditorOption; @@ -3910,10 +4015,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; @@ -4866,6 +4973,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). */ @@ -5289,7 +5401,9 @@ declare namespace monaco.languages { Customcolor = 22, Folder = 23, TypeParameter = 24, - Snippet = 25 + User = 25, + Issue = 26, + Snippet = 27 } export interface CompletionItemLabel { @@ -5298,9 +5412,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`. */ @@ -5377,7 +5491,7 @@ declare namespace monaco.languages { preselect?: boolean; /** * A string or snippet that should be inserted in a document when selecting - * this completion. When `falsy` the [label](#CompletionItem.label) + * this completion. * is used. */ insertText: string; @@ -5624,6 +5738,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. @@ -5961,11 +6087,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; } @@ -6017,7 +6143,7 @@ declare namespace monaco.languages { description?: string; iconPath?: { id: string; - } | { + } | Uri | { light: Uri; dark: Uri; }; @@ -6108,6 +6234,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 25a4db0972c..b8109ba8e32 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -7,35 +7,37 @@ import { Action } from 'vs/base/common/actions'; import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IConstructorSignature2, createDecorator, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { UriDto } from 'vs/base/common/types'; export interface ILocalizedString { value: string; 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; - precondition?: ContextKeyExpr; - toggled?: ContextKeyExpr; + tooltip?: string | ILocalizedString; + icon?: Icon; + precondition?: ContextKeyExpression; + toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString }; } -type Serialized = { [K in keyof T]: T[K] extends URI ? UriComponents : Serialized }; - -export type ISerializableCommandAction = Serialized; +export type ISerializableCommandAction = UriDto; export interface IMenuItem { command: ICommandAction; alt?: ICommandAction; - when?: ContextKeyExpr; + when?: ContextKeyExpression; group?: 'navigation' | string; order?: number; } @@ -43,7 +45,7 @@ export interface IMenuItem { export interface ISubmenuItem { title: string | ILocalizedString; submenu: MenuId; - when?: ContextKeyExpr; + when?: ContextKeyExpression; group?: 'navigation' | string; order?: number; } @@ -90,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'); @@ -112,9 +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; @@ -274,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 || {}; @@ -313,17 +331,17 @@ export class SyncActionDescriptor { private readonly _id: string; private readonly _label?: string; private readonly _keybindings: IKeybindings | undefined; - private readonly _keybindingContext: ContextKeyExpr | undefined; + private readonly _keybindingContext: ContextKeyExpression | undefined; private readonly _keybindingWeight: number | undefined; public static create(ctor: { new(id: string, label: string, ...services: Services): Action }, - id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number + id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpression, keybindingWeight?: number ): SyncActionDescriptor { return new SyncActionDescriptor(ctor as IConstructorSignature2, id, label, keybindings, keybindingContext, keybindingWeight); } private constructor(ctor: IConstructorSignature2, - id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number + id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpression, keybindingWeight?: number ) { this._id = id; this._label = label; @@ -349,7 +367,7 @@ export class SyncActionDescriptor { return this._keybindings; } - public get keybindingContext(): ContextKeyExpr | undefined { + public get keybindingContext(): ContextKeyExpression | undefined { return this._keybindingContext; } @@ -395,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 fac16754098..96987a4c911 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContextKeyService, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKeyChangeEvent, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class MenuService implements IMenuService { @@ -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); @@ -125,7 +126,7 @@ class Menu implements IMenu { return result; } - private static _fillInKbExprKeys(exp: ContextKeyExpr | undefined, set: Set): void { + private static _fillInKbExprKeys(exp: ContextKeyExpression | undefined, set: Set): void { if (exp) { for (let key of exp.keys()) { set.add(key); diff --git a/src/vs/platform/userDataSync/common/userDataAuthTokenService.ts b/src/vs/platform/authentication/common/authentication.ts similarity index 55% rename from src/vs/platform/userDataSync/common/userDataAuthTokenService.ts rename to src/vs/platform/authentication/common/authentication.ts index 03ba0c45dfe..25483b4177c 100644 --- a/src/vs/platform/userDataSync/common/userDataAuthTokenService.ts +++ b/src/vs/platform/authentication/common/authentication.ts @@ -3,17 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; -export class UserDataAuthTokenService extends Disposable implements IUserDataAuthTokenService { +export const IAuthenticationTokenService = createDecorator('IAuthenticationTokenService'); + +export interface IAuthenticationTokenService { + _serviceBrand: undefined; + + readonly onDidChangeToken: Event; + readonly onTokenFailed: Event; + + getToken(): Promise; + setToken(accessToken: string | undefined): Promise; + sendTokenFailed(): void; +} + +export class AuthenticationTokenService extends Disposable implements IAuthenticationTokenService { _serviceBrand: any; private _onDidChangeToken: Emitter = this._register(new Emitter()); readonly onDidChangeToken: Event = this._onDidChangeToken.event; + private _onTokenFailed: Emitter = this._register(new Emitter()); + readonly onTokenFailed: Event = this._onTokenFailed.event; + private _token: string | undefined; constructor() { @@ -30,4 +46,9 @@ export class UserDataAuthTokenService extends Disposable implements IUserDataAut this._onDidChangeToken.fire(token); } } + + sendTokenFailed(): void { + this._onTokenFailed.fire(); + } } + diff --git a/src/vs/platform/authentication/common/authenticationIpc.ts b/src/vs/platform/authentication/common/authenticationIpc.ts new file mode 100644 index 00000000000..9836ee14682 --- /dev/null +++ b/src/vs/platform/authentication/common/authenticationIpc.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; + + +export class AuthenticationTokenServiceChannel implements IServerChannel { + constructor(private readonly service: IAuthenticationTokenService) { } + + listen(_: unknown, event: string): Event { + switch (event) { + case 'onDidChangeToken': return this.service.onDidChangeToken; + case 'onTokenFailed': return this.service.onTokenFailed; + } + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'setToken': return this.service.setToken(args); + case 'getToken': return this.service.getToken(); + } + throw new Error('Invalid call'); + } +} 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..a2458c46741 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -8,10 +8,10 @@ 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 { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; @@ -28,12 +28,12 @@ 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, + @IEnvironmentService environmentService: INativeEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, @ILogService private readonly logService: ILogService ) { @@ -51,31 +51,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 +93,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 +106,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 +116,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 +134,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 +188,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 +205,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 +216,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 +225,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 +255,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 +287,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 +325,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 +350,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 +362,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 +371,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 +383,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 +468,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 new file mode 100644 index 00000000000..42c52b3b6ef --- /dev/null +++ b/src/vs/platform/clipboard/browser/clipboardService.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { URI } from 'vs/base/common/uri'; + +export class BrowserClipboardService implements IClipboardService { + + _serviceBrand: undefined; + + private _internalResourcesClipboard: URI[] | undefined; + + async writeText(text: string, type?: string): Promise { + if (type) { + return; // TODO@sbatten + } + + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } else { + const activeElement = document.activeElement; + const newTextarea = document.createElement('textarea'); + newTextarea.className = 'clipboard-copy'; + newTextarea.style.visibility = 'false'; + newTextarea.style.height = '1px'; + newTextarea.style.width = '1px'; + newTextarea.setAttribute('aria-hidden', 'true'); + newTextarea.style.position = 'absolute'; + newTextarea.style.top = '-1000'; + newTextarea.style.left = '-1000'; + document.body.appendChild(newTextarea); + newTextarea.value = text; + newTextarea.focus(); + newTextarea.select(); + document.execCommand('copy'); + activeElement.focus(); + document.body.removeChild(newTextarea); + } + return; + } + + async readText(type?: string): Promise { + if (type) { + return ''; // TODO@sbatten + } + + return navigator.clipboard.readText(); + } + + readTextSync(): string | undefined { + return undefined; + } + + readFindText(): string { + // @ts-expect-error + return undefined; + } + + writeFindText(text: string): void { } + + writeResources(resources: URI[]): void { + this._internalResourcesClipboard = resources; + } + + readResources(): URI[] { + return this._internalResourcesClipboard || []; + } + + hasResources(): boolean { + return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0; + } +} 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/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 7ed8dbd38a9..9998707a68b 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -352,7 +352,7 @@ export class UserSettings extends Disposable { super(); this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); this._register(this.fileService.watch(dirname(this.userSettingsResource))); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this._onDidChange.fire())); + this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.userSettingsResource))(() => this._onDidChange.fire())); } async loadConfiguration(): Promise { diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index abcae819e57..05975998d51 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -113,6 +113,7 @@ export interface IConfigurationPropertySchema extends IJSONSchema { scope?: ConfigurationScope; included?: boolean; tags?: string[]; + disallowSyncIgnore?: boolean; } export interface IConfigurationExtensionInfo { diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts similarity index 100% rename from src/vs/platform/configuration/node/configurationService.ts rename to src/vs/platform/configuration/common/configurationService.ts diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index 01b942cf531..c8e4cfd5dde 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -18,7 +18,7 @@ export class TestConfigurationService implements IConfigurationService { this.configuration = configuration || Object.create(null); } - private configurationByRoot: TernarySearchTree = TernarySearchTree.forPaths(); + private configurationByRoot: TernarySearchTree = TernarySearchTree.forPaths(); public reloadConfiguration(): Promise { return Promise.resolve(this.getValue()); diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index f2e90b7d245..f5fb471fcdb 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -9,7 +9,7 @@ import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { testFile } from 'vs/base/test/node/utils'; @@ -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 6e033c32c6a..78f7843ea46 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -8,7 +8,7 @@ import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { keys } from 'vs/base/common/map'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID } from 'vs/platform/contextkey/common/contextkey'; +import { IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context'; @@ -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); @@ -265,7 +269,7 @@ export abstract class AbstractContextKeyService implements IContextKeyService { return new ScopedContextKeyService(this, domNode); } - public contextMatchesRules(rules: ContextKeyExpr | undefined): boolean { + public contextMatchesRules(rules: ContextKeyExpression | undefined): boolean { if (this._isDisposed) { throw new Error(`AbstractContextKeyService has been disposed`); } diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 784f586779a..414b6a3d3c0 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -6,57 +6,94 @@ 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, isWeb } from 'vs/base/common/platform'; + +const STATIC_VALUES = new Map(); +STATIC_VALUES.set('false', false); +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 { - Defined = 1, - Not = 2, - Equals = 3, - NotEquals = 4, - And = 5, - Regex = 6, - NotRegex = 7, - Or = 8 + False = 0, + True = 1, + Defined = 2, + Not = 3, + Equals = 4, + NotEquals = 5, + And = 6, + Regex = 7, + NotRegex = 8, + Or = 9 } export interface IContextKeyExprMapper { - mapDefined(key: string): ContextKeyExpr; - mapNot(key: string): ContextKeyExpr; - mapEquals(key: string, value: any): ContextKeyExpr; - mapNotEquals(key: string, value: any): ContextKeyExpr; + mapDefined(key: string): ContextKeyExpression; + mapNot(key: string): ContextKeyExpression; + mapEquals(key: string, value: any): ContextKeyExpression; + mapNotEquals(key: string, value: any): ContextKeyExpression; mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr; } +export interface IContextKeyExpression { + cmp(other: ContextKeyExpression): number; + equals(other: ContextKeyExpression): boolean; + evaluate(context: IContext): boolean; + serialize(): string; + keys(): string[]; + map(mapFnc: IContextKeyExprMapper): ContextKeyExpression; + negate(): ContextKeyExpression; + +} + +export type ContextKeyExpression = ( + ContextKeyFalseExpr | ContextKeyTrueExpr | ContextKeyDefinedExpr | ContextKeyNotExpr + | ContextKeyEqualsExpr | ContextKeyNotEqualsExpr | ContextKeyRegexExpr + | ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr +); + export abstract class ContextKeyExpr { - public static has(key: string): ContextKeyExpr { + public static false(): ContextKeyExpression { + return ContextKeyFalseExpr.INSTANCE; + } + + public static true(): ContextKeyExpression { + return ContextKeyTrueExpr.INSTANCE; + } + + public static has(key: string): ContextKeyExpression { return ContextKeyDefinedExpr.create(key); } - public static equals(key: string, value: any): ContextKeyExpr { + public static equals(key: string, value: any): ContextKeyExpression { return ContextKeyEqualsExpr.create(key, value); } - public static notEquals(key: string, value: any): ContextKeyExpr { + public static notEquals(key: string, value: any): ContextKeyExpression { return ContextKeyNotEqualsExpr.create(key, value); } - public static regex(key: string, value: RegExp): ContextKeyExpr { + public static regex(key: string, value: RegExp): ContextKeyExpression { return ContextKeyRegexExpr.create(key, value); } - public static not(key: string): ContextKeyExpr { + public static not(key: string): ContextKeyExpression { return ContextKeyNotExpr.create(key); } - public static and(...expr: Array): ContextKeyExpr | undefined { + public static and(...expr: Array): ContextKeyExpression | undefined { return ContextKeyAndExpr.create(expr); } - public static or(...expr: Array): ContextKeyExpr | undefined { + public static or(...expr: Array): ContextKeyExpression | undefined { return ContextKeyOrExpr.create(expr); } - public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpr | undefined { + public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpression | undefined { if (!serialized) { return undefined; } @@ -64,17 +101,17 @@ export abstract class ContextKeyExpr { return this._deserializeOrExpression(serialized, strict); } - private static _deserializeOrExpression(serialized: string, strict: boolean): ContextKeyExpr | undefined { + private static _deserializeOrExpression(serialized: string, strict: boolean): ContextKeyExpression | undefined { let pieces = serialized.split('||'); return ContextKeyOrExpr.create(pieces.map(p => this._deserializeAndExpression(p, strict))); } - private static _deserializeAndExpression(serialized: string, strict: boolean): ContextKeyExpr | undefined { + private static _deserializeAndExpression(serialized: string, strict: boolean): ContextKeyExpression | undefined { let pieces = serialized.split('&&'); return ContextKeyAndExpr.create(pieces.map(p => this._deserializeOne(p, strict))); } - private static _deserializeOne(serializedOne: string, strict: boolean): ContextKeyExpr { + private static _deserializeOne(serializedOne: string, strict: boolean): ContextKeyExpression { serializedOne = serializedOne.trim(); if (serializedOne.indexOf('!=') >= 0) { @@ -153,55 +190,104 @@ export abstract class ContextKeyExpr { return null; } } - - public abstract getType(): ContextKeyExprType; - public abstract equals(other: ContextKeyExpr): boolean; - public abstract evaluate(context: IContext): boolean; - public abstract serialize(): string; - public abstract keys(): string[]; - public abstract map(mapFnc: IContextKeyExprMapper): ContextKeyExpr; - public abstract negate(): ContextKeyExpr; } -function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number { - let aType = a.getType(); - let bType = b.getType(); - if (aType !== bType) { - return aType - bType; +function cmp(a: ContextKeyExpression, b: ContextKeyExpression): number { + return a.cmp(b); +} + +export class ContextKeyFalseExpr implements IContextKeyExpression { + public static INSTANCE = new ContextKeyFalseExpr(); + + public readonly type = ContextKeyExprType.False; + + protected constructor() { } - switch (aType) { - case ContextKeyExprType.Defined: - return (a).cmp(b); - case ContextKeyExprType.Not: - return (a).cmp(b); - case ContextKeyExprType.Equals: - return (a).cmp(b); - case ContextKeyExprType.NotEquals: - return (a).cmp(b); - case ContextKeyExprType.Regex: - return (a).cmp(b); - case ContextKeyExprType.NotRegex: - return (a).cmp(b); - case ContextKeyExprType.And: - return (a).cmp(b); - default: - throw new Error('Unknown ContextKeyExpr!'); + + public cmp(other: ContextKeyExpression): number { + return this.type - other.type; + } + + public equals(other: ContextKeyExpression): boolean { + return (other.type === this.type); + } + + public evaluate(context: IContext): boolean { + return false; + } + + public serialize(): string { + return 'false'; + } + + public keys(): string[] { + return []; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { + return this; + } + + public negate(): ContextKeyExpression { + return ContextKeyTrueExpr.INSTANCE; } } -export class ContextKeyDefinedExpr implements ContextKeyExpr { - public static create(key: string): ContextKeyDefinedExpr { +export class ContextKeyTrueExpr implements IContextKeyExpression { + public static INSTANCE = new ContextKeyTrueExpr(); + + public readonly type = ContextKeyExprType.True; + + protected constructor() { + } + + public cmp(other: ContextKeyExpression): number { + return this.type - other.type; + } + + public equals(other: ContextKeyExpression): boolean { + return (other.type === this.type); + } + + public evaluate(context: IContext): boolean { + return true; + } + + public serialize(): string { + return 'true'; + } + + public keys(): string[] { + return []; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { + return this; + } + + public negate(): ContextKeyExpression { + return ContextKeyFalseExpr.INSTANCE; + } +} + +export class ContextKeyDefinedExpr implements IContextKeyExpression { + public static create(key: string): ContextKeyExpression { + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + return staticValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE; + } return new ContextKeyDefinedExpr(key); } - protected constructor(protected key: string) { + public readonly type = ContextKeyExprType.Defined; + + protected constructor(protected readonly key: string) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Defined; - } - - public cmp(other: ContextKeyDefinedExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -211,8 +297,8 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyDefinedExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key); } return false; @@ -230,35 +316,38 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapDefined(this.key); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyNotExpr.create(this.key); } } -export class ContextKeyEqualsExpr implements ContextKeyExpr { +export class ContextKeyEqualsExpr implements IContextKeyExpression { - public static create(key: string, value: any): ContextKeyExpr { + public static create(key: string, value: any): ContextKeyExpression { if (typeof value === 'boolean') { - if (value) { - return ContextKeyDefinedExpr.create(key); - } - return ContextKeyNotExpr.create(key); + return (value ? ContextKeyDefinedExpr.create(key) : ContextKeyNotExpr.create(key)); + } + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + const trueValue = staticValue ? 'true' : 'false'; + return (value === trueValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE); } return new ContextKeyEqualsExpr(key, value); } + public readonly type = ContextKeyExprType.Equals; + private constructor(private readonly key: string, private readonly value: any) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Equals; - } - - public cmp(other: ContextKeyEqualsExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -274,8 +363,8 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyEqualsExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key && this.value === other.value); } return false; @@ -295,35 +384,41 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapEquals(this.key, this.value); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyNotEqualsExpr.create(this.key, this.value); } } -export class ContextKeyNotEqualsExpr implements ContextKeyExpr { +export class ContextKeyNotEqualsExpr implements IContextKeyExpression { - public static create(key: string, value: any): ContextKeyExpr { + public static create(key: string, value: any): ContextKeyExpression { if (typeof value === 'boolean') { if (value) { return ContextKeyNotExpr.create(key); } return ContextKeyDefinedExpr.create(key); } + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + const falseValue = staticValue ? 'true' : 'false'; + return (value === falseValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE); + } return new ContextKeyNotEqualsExpr(key, value); } - private constructor(private key: string, private value: any) { + public readonly type = ContextKeyExprType.NotEquals; + + private constructor(private readonly key: string, private readonly value: any) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.NotEquals; - } - - public cmp(other: ContextKeyNotEqualsExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -339,8 +434,8 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyNotEqualsExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key && this.value === other.value); } return false; @@ -360,29 +455,34 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapNotEquals(this.key, this.value); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyEqualsExpr.create(this.key, this.value); } } -export class ContextKeyNotExpr implements ContextKeyExpr { +export class ContextKeyNotExpr implements IContextKeyExpression { - public static create(key: string): ContextKeyExpr { + public static create(key: string): ContextKeyExpression { + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + return (staticValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE); + } return new ContextKeyNotExpr(key); } - private constructor(private key: string) { + public readonly type = ContextKeyExprType.Not; + + private constructor(private readonly key: string) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Not; - } - - public cmp(other: ContextKeyNotExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -392,8 +492,8 @@ export class ContextKeyNotExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyNotExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key); } return false; @@ -411,30 +511,31 @@ export class ContextKeyNotExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapNot(this.key); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyDefinedExpr.create(this.key); } } -export class ContextKeyRegexExpr implements ContextKeyExpr { +export class ContextKeyRegexExpr implements IContextKeyExpression { public static create(key: string, regexp: RegExp | null): ContextKeyRegexExpr { return new ContextKeyRegexExpr(key, regexp); } - private constructor(private key: string, private regexp: RegExp | null) { + public readonly type = ContextKeyExprType.Regex; + + private constructor(private readonly key: string, private readonly regexp: RegExp | null) { // } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Regex; - } - - public cmp(other: ContextKeyRegexExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -452,8 +553,8 @@ export class ContextKeyRegexExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyRegexExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { const thisSource = this.regexp ? this.regexp.source : ''; const otherSource = other.regexp ? other.regexp.source : ''; return (this.key === other.key && thisSource === otherSource); @@ -481,31 +582,32 @@ export class ContextKeyRegexExpr implements ContextKeyExpr { return mapFnc.mapRegex(this.key, this.regexp); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyNotRegexExpr.create(this); } } -export class ContextKeyNotRegexExpr implements ContextKeyExpr { +export class ContextKeyNotRegexExpr implements IContextKeyExpression { - public static create(actual: ContextKeyRegexExpr): ContextKeyExpr { + public static create(actual: ContextKeyRegexExpr): ContextKeyExpression { return new ContextKeyNotRegexExpr(actual); } + public readonly type = ContextKeyExprType.NotRegex; + private constructor(private readonly _actual: ContextKeyRegexExpr) { // } - public getType(): ContextKeyExprType { - return ContextKeyExprType.NotRegex; - } - - public cmp(other: ContextKeyNotRegexExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } return this._actual.cmp(other._actual); } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyNotRegexExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return this._actual.equals(other._actual); } return false; @@ -523,18 +625,18 @@ export class ContextKeyNotRegexExpr implements ContextKeyExpr { return this._actual.keys(); } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyNotRegexExpr(this._actual.map(mapFnc)); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return this._actual; } } -export class ContextKeyAndExpr implements ContextKeyExpr { +export class ContextKeyAndExpr implements IContextKeyExpression { - public static create(_expr: ReadonlyArray): ContextKeyExpr | undefined { + public static create(_expr: ReadonlyArray): ContextKeyExpression | undefined { const expr = ContextKeyAndExpr._normalizeArr(_expr); if (expr.length === 0) { return undefined; @@ -547,14 +649,15 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return new ContextKeyAndExpr(expr); } - private constructor(public readonly expr: ContextKeyExpr[]) { + public readonly type = ContextKeyExprType.And; + + private constructor(public readonly expr: ContextKeyExpression[]) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.And; - } - - public cmp(other: ContextKeyAndExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.expr.length < other.expr.length) { return -1; } @@ -570,8 +673,8 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyAndExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { if (this.expr.length !== other.expr.length) { return false; } @@ -594,20 +697,32 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return true; } - private static _normalizeArr(arr: ReadonlyArray): ContextKeyExpr[] { - const expr: ContextKeyExpr[] = []; + private static _normalizeArr(arr: ReadonlyArray): ContextKeyExpression[] { + const expr: ContextKeyExpression[] = []; + let hasTrue = false; for (const e of arr) { if (!e) { continue; } - if (e instanceof ContextKeyAndExpr) { + if (e.type === ContextKeyExprType.True) { + // anything && true ==> anything + hasTrue = true; + continue; + } + + if (e.type === ContextKeyExprType.False) { + // anything && false ==> false + return [ContextKeyFalseExpr.INSTANCE]; + } + + if (e.type === ContextKeyExprType.And) { expr.push(...e.expr); continue; } - if (e instanceof ContextKeyOrExpr) { + if (e.type === ContextKeyExprType.Or) { // Not allowed, because we don't have parens! throw new Error(`It is not allowed to have an or expression here due to lack of parens! For example "a && (b||c)" is not supported, use "(a&&b) || (a&&c)" instead.`); } @@ -615,6 +730,10 @@ export class ContextKeyAndExpr implements ContextKeyExpr { expr.push(e); } + if (expr.length === 0 && hasTrue) { + return [ContextKeyTrueExpr.INSTANCE]; + } + expr.sort(cmp); return expr; @@ -632,12 +751,12 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return result; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyAndExpr(this.expr.map(expr => expr.map(mapFnc))); } - public negate(): ContextKeyExpr { - let result: ContextKeyExpr[] = []; + public negate(): ContextKeyExpression { + let result: ContextKeyExpression[] = []; for (let expr of this.expr) { result.push(expr.negate()); } @@ -645,9 +764,9 @@ export class ContextKeyAndExpr implements ContextKeyExpr { } } -export class ContextKeyOrExpr implements ContextKeyExpr { +export class ContextKeyOrExpr implements IContextKeyExpression { - public static create(_expr: ReadonlyArray): ContextKeyExpr | undefined { + public static create(_expr: ReadonlyArray): ContextKeyExpression | undefined { const expr = ContextKeyOrExpr._normalizeArr(_expr); if (expr.length === 0) { return undefined; @@ -660,15 +779,32 @@ export class ContextKeyOrExpr implements ContextKeyExpr { return new ContextKeyOrExpr(expr); } - private constructor(public readonly expr: ContextKeyExpr[]) { + public readonly type = ContextKeyExprType.Or; + + private constructor(public readonly expr: ContextKeyExpression[]) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Or; + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } + if (this.expr.length < other.expr.length) { + return -1; + } + if (this.expr.length > other.expr.length) { + return 1; + } + for (let i = 0, len = this.expr.length; i < len; i++) { + const r = cmp(this.expr[i], other.expr[i]); + if (r !== 0) { + return r; + } + } + return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyOrExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { if (this.expr.length !== other.expr.length) { return false; } @@ -691,17 +827,29 @@ export class ContextKeyOrExpr implements ContextKeyExpr { return false; } - private static _normalizeArr(arr: ReadonlyArray): ContextKeyExpr[] { - let expr: ContextKeyExpr[] = []; + private static _normalizeArr(arr: ReadonlyArray): ContextKeyExpression[] { + let expr: ContextKeyExpression[] = []; + let hasFalse = false; if (arr) { for (let i = 0, len = arr.length; i < len; i++) { - let e: ContextKeyExpr | null | undefined = arr[i]; + const e = arr[i]; if (!e) { continue; } - if (e instanceof ContextKeyOrExpr) { + if (e.type === ContextKeyExprType.False) { + // anything || false ==> anything + hasFalse = true; + continue; + } + + if (e.type === ContextKeyExprType.True) { + // anything || true ==> true + return [ContextKeyTrueExpr.INSTANCE]; + } + + if (e.type === ContextKeyExprType.Or) { expr = expr.concat(e.expr); continue; } @@ -709,6 +857,10 @@ export class ContextKeyOrExpr implements ContextKeyExpr { expr.push(e); } + if (expr.length === 0 && hasFalse) { + return [ContextKeyFalseExpr.INSTANCE]; + } + expr.sort(cmp); } @@ -727,18 +879,18 @@ export class ContextKeyOrExpr implements ContextKeyExpr { return result; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyOrExpr(this.expr.map(expr => expr.map(mapFnc))); } - public negate(): ContextKeyExpr { - let result: ContextKeyExpr[] = []; + public negate(): ContextKeyExpression { + let result: ContextKeyExpression[] = []; for (let expr of this.expr) { result.push(expr.negate()); } - const terminals = (node: ContextKeyExpr) => { - if (node instanceof ContextKeyOrExpr) { + const terminals = (node: ContextKeyExpression) => { + if (node.type === ContextKeyExprType.Or) { return node.expr; } return [node]; @@ -750,7 +902,7 @@ export class ContextKeyOrExpr implements ContextKeyExpr { const LEFT = result.shift()!; const RIGHT = result.shift()!; - const all: ContextKeyExpr[] = []; + const all: ContextKeyExpression[] = []; for (const left of terminals(LEFT)) { for (const right of terminals(RIGHT)) { all.push(ContextKeyExpr.and(left, right)!); @@ -765,7 +917,7 @@ export class ContextKeyOrExpr implements ContextKeyExpr { export class RawContextKey extends ContextKeyDefinedExpr { - private _defaultValue: T | undefined; + private readonly _defaultValue: T | undefined; constructor(key: string, defaultValue: T | undefined) { super(key); @@ -780,15 +932,15 @@ export class RawContextKey extends ContextKeyDefinedExpr { return target.getContextKeyValue(this.key); } - public toNegated(): ContextKeyExpr { + public toNegated(): ContextKeyExpression { return ContextKeyExpr.not(this.key); } - public isEqualTo(value: string): ContextKeyExpr { + public isEqualTo(value: string): ContextKeyExpression { return ContextKeyExpr.equals(this.key, value); } - public notEqualsTo(value: string): ContextKeyExpr { + public notEqualsTo(value: string): ContextKeyExpression { return ContextKeyExpr.notEquals(this.key, value); } } @@ -830,7 +982,7 @@ export interface IContextKeyService { createKey(key: string, defaultValue: T | undefined): IContextKey; - contextMatchesRules(rules: ContextKeyExpr | undefined): boolean; + contextMatchesRules(rules: ContextKeyExpression | undefined): boolean; getContextKeyValue(key: string): T | undefined; createScoped(target?: IContextKeyServiceTarget): IContextKeyService; diff --git a/src/vs/platform/contextkey/common/contextkeys.ts b/src/vs/platform/contextkey/common/contextkeys.ts index c3206a24d9a..8c17906ab67 100644 --- a/src/vs/platform/contextkey/common/contextkeys.ts +++ b/src/vs/platform/contextkey/common/contextkeys.ts @@ -3,9 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +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: ContextKeyExpr = new RawContextKey('__false', false); \ No newline at end of file diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts index 72929eff428..f5a04ce1cf2 100644 --- a/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; function createContext(ctx: any) { return { @@ -89,6 +90,8 @@ suite('ContextKeyExpr', () => { testBatch('d', 'd'); testBatch('z', undefined); + testExpression('true', true); + testExpression('false', false); testExpression('a && !b', true && !false); testExpression('a && b', true && false); testExpression('a && !b && c == 5', true && !false && '5' === '5'); @@ -107,10 +110,30 @@ suite('ContextKeyExpr', () => { const actual = ContextKeyExpr.deserialize(expr)!.negate().serialize(); assert.strictEqual(actual, expected); } + testNegate('true', 'false'); + testNegate('false', 'true'); testNegate('a', '!a'); testNegate('a && b || c', '!a && !c || !b && !c'); testNegate('a && b || c || d', '!a && !c && !d || !b && !c && !d'); testNegate('!a && !b || !c && !d', 'a && c || a && d || b && c || b && d'); testNegate('!a && !b || !c && !d || !e && !f', 'a && c && e || a && c && f || a && d && e || a && d && f || b && c && e || b && c && f || b && d && e || b && d && f'); }); + + test('false, true', () => { + function testNormalize(expr: string, expected: string): void { + const actual = ContextKeyExpr.deserialize(expr)!.serialize(); + assert.strictEqual(actual, expected); + } + testNormalize('true', 'true'); + testNormalize('!true', 'false'); + testNormalize('false', 'false'); + testNormalize('!false', 'true'); + testNormalize('a && true', 'a'); + testNormalize('a && false', 'false'); + testNormalize('a || true', 'true'); + testNormalize('a || false', 'a'); + testNormalize('isMac', isMacintosh ? 'true' : 'false'); + testNormalize('isLinux', isLinux ? 'true' : 'false'); + testNormalize('isWindows', isWindows ? 'true' : 'false'); + }); }); diff --git a/src/vs/platform/diagnostics/common/diagnostics.ts b/src/vs/platform/diagnostics/common/diagnostics.ts index 31a4ee17474..c283b80e6ef 100644 --- a/src/vs/platform/diagnostics/common/diagnostics.ts +++ b/src/vs/platform/diagnostics/common/diagnostics.ts @@ -13,6 +13,14 @@ export interface IMachineInfo { cpus?: string; memory: string; vmHint: string; + linuxEnv?: ILinuxEnv; +} + +export interface ILinuxEnv { + desktopSession?: string; + xdgSessionDesktop?: string; + xdgCurrentDesktop?: string; + xdgSessionType?: string; } export interface IDiagnosticInfo { diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index b423cbae2eb..6fddf2b2822 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -11,7 +11,7 @@ import { parse, ParseError, getNodeType } from 'vs/base/common/json'; import { listProcesses } from 'vs/base/node/ps'; import product from 'vs/platform/product/common/product'; import { repeat, pad } from 'vs/base/common/strings'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; import { IMainProcessInfo } from 'vs/platform/launch/common/launch'; @@ -336,11 +336,19 @@ export class DiagnosticsService implements IDiagnosticsService { remoteData }; - if (!isWindows) { systemInfo.load = `${osLib.loadavg().map(l => Math.round(l)).join(', ')}`; } + if (isLinux) { + systemInfo.linuxEnv = { + desktopSession: process.env.DESKTOP_SESSION, + xdgSessionDesktop: process.env.XDG_SESSION_DESKTOP, + xdgCurrentDesktop: process.env.XDG_CURRENT_DESKTOP, + xdgSessionType: process.env.XDG_SESSION_TYPE + }; + } + return Promise.resolve(systemInfo); } diff --git a/src/vs/platform/dialogs/test/common/testDialogService.ts b/src/vs/platform/dialogs/test/common/testDialogService.ts new file mode 100644 index 00000000000..0f1cbc0f992 --- /dev/null +++ b/src/vs/platform/dialogs/test/common/testDialogService.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Severity from 'vs/base/common/severity'; +import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs'; + +export class TestDialogService implements IDialogService { + + _serviceBrand: undefined; + + confirm(_confirmation: IConfirmation): Promise { return Promise.resolve({ confirmed: false }); } + show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } + about(): Promise { return Promise.resolve(); } +} diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index bae55607623..b5a041bf44c 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -13,7 +13,7 @@ import { SimpleKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { OS } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; @@ -207,7 +207,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { export async function serve( windowServer: IPCServer, handle: string, - environmentService: IEnvironmentService, + environmentService: INativeEnvironmentService, instantiationService: IInstantiationService ): Promise { const verbose = environmentService.driverVerbose; diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index f6018b7bd19..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. @@ -228,6 +228,11 @@ export const enum TextEditorSelectionRevealType { * Option to scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, but not quite at the top. */ NearTop = 2, + /** + * Option to scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, but not quite at the top. + * Only if it lies outside the viewport + */ + NearTopIfOutsideViewport = 3, } export interface ITextEditorOptions extends IEditorOptions { diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 6a7310f2dc7..a8cc247f89d 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -6,9 +6,9 @@ import { Event } from 'vs/base/common/event'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app } from 'electron'; -import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; +import { INativeOpenWindowOptions, IOpenedWindow, OpenContext } from 'vs/platform/windows/node/window'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { isMacintosh } from 'vs/base/common/platform'; import { IElectronService } from 'vs/platform/electron/node/electron'; @@ -20,8 +20,10 @@ 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'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } +export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } export const IElectronMainService = createDecorator('electronMainService'); @@ -33,8 +35,9 @@ export class ElectronMainService implements IElectronMainService { @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ILogService private readonly logService: ILogService ) { } @@ -63,7 +66,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 +273,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 +334,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 +400,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..43203239b81 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -6,10 +6,10 @@ import { Event } from 'vs/base/common/event'; import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions } from 'electron'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; +import { IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; +import { INativeOpenWindowOptions, IOpenedWindow } from 'vs/platform/windows/node/window'; export const IElectronService = createDecorator('electronService'); @@ -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..c64c5fc6815 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -5,91 +5,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { IUserHomeProvider } from 'vs/base/common/labels'; - -export interface ParsedArgs { - _: string[]; - 'folder-uri'?: string[]; // undefined or array of 1 or more - 'file-uri'?: string[]; // undefined or array of 1 or more - _urls?: string[]; - help?: boolean; - version?: boolean; - telemetry?: boolean; - status?: boolean; - wait?: boolean; - waitMarkerFilePath?: string; - diff?: boolean; - add?: boolean; - goto?: boolean; - 'new-window'?: boolean; - 'unity-launch'?: boolean; // Always open a new window, except if opening the first window or opening a file or folder as part of the launch. - 'reuse-window'?: boolean; - locale?: string; - 'user-data-dir'?: string; - 'prof-startup'?: boolean; - 'prof-startup-prefix'?: string; - 'prof-append-timers'?: string; - verbose?: boolean; - trace?: boolean; - 'trace-category-filter'?: string; - 'trace-options'?: string; - log?: string; - logExtensionHostCommunication?: boolean; - 'extensions-dir'?: string; - '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; - 'inspect-search'?: string; - 'inspect-brk-search'?: string; - 'disable-extensions'?: boolean; - 'disable-extension'?: string[]; // undefined or array of 1 or more - 'list-extensions'?: boolean; - 'show-versions'?: boolean; - 'category'?: string; - 'install-extension'?: string[]; // undefined or array of 1 or more - 'uninstall-extension'?: string[]; // undefined or array of 1 or more - 'locate-extension'?: string[]; // undefined or array of 1 or more - 'enable-proposed-api'?: string[]; // undefined or array of 1 or more - 'open-url'?: boolean; - 'skip-getting-started'?: boolean; - 'skip-release-notes'?: boolean; - 'sticky-quickopen'?: boolean; - 'disable-restore-windows'?: boolean; - 'disable-telemetry'?: boolean; - 'export-default-configuration'?: string; - 'install-source'?: string; - 'disable-updates'?: boolean; - 'disable-crash-reporter'?: boolean; - 'skip-add-to-recently-opened'?: boolean; - 'max-memory'?: string; - 'file-write'?: boolean; - 'file-chmod'?: boolean; - 'driver'?: string; - 'driver-verbose'?: boolean; - remote?: string; - 'disable-user-env-probe'?: boolean; - 'force'?: boolean; - 'force-user-env'?: boolean; - - // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches - 'no-proxy-server'?: boolean; - 'proxy-server'?: string; - 'proxy-bypass-list'?: string; - 'proxy-pac-url'?: string; - 'inspect'?: string; - 'inspect-brk'?: string; - 'js-flags'?: string; - 'disable-gpu'?: boolean; - 'nolazy'?: boolean; - 'force-device-scale-factor'?: string; - 'force-renderer-accessibility'?: boolean; - 'ignore-certificate-error'?: boolean; - 'allow-insecure-localhost'?: boolean; -} export const IEnvironmentService = createDecorator('environmentService'); @@ -104,74 +19,59 @@ export interface IExtensionHostDebugParams extends IDebugParams { export const BACKUPS = 'Backups'; -export interface IEnvironmentService extends IUserHomeProvider { +export interface IEnvironmentService { + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: DO NOT ADD ANY OTHER PROPERTY INTO THE COLLECTION HERE + // UNLESS THIS PROPERTY IS SUPPORTED BOTH IN WEB AND DESKTOP!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! _serviceBrand: undefined; - args: ParsedArgs; - - execPath: string; - cliPath: string; - appRoot: string; - - userHome: string; - userDataPath: string; - - appSettingsHome: URI; - - // user roaming data + // --- user roaming data userRoamingDataHome: URI; settingsResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; argvResource: URI; + snippetsHome: URI; - // sync resources + // --- settings sync userDataSyncLogResource: URI; userDataSyncHome: URI; - settingsSyncPreviewResource: URI; - keybindingsSyncPreviewResource: URI; - - machineSettingsHome: URI; - machineSettingsResource: URI; - - globalStorageHome: string; - workspaceStorageHome: string; - - backupHome: URI; - backupWorkspacesPath: string; - - untitledWorkspacesHome: URI; + sync: 'on' | 'off'; + // --- extension development + debugExtensionHost: IExtensionHostDebugParams; isExtensionDevelopment: boolean; disableExtensions: boolean | string[]; - builtinExtensionsPath: string; - extensionsPath?: string; extensionDevelopmentLocationURI?: URI[]; extensionTestsLocationURI?: URI; + extensionEnabledProposedApi?: string[]; logExtensionHostCommunication?: boolean; - debugExtensionHost: IExtensionHostDebugParams; - - isBuilt: boolean; - wait: boolean; - status: boolean; - - log?: string; + // --- logging logsPath: string; + logLevel?: string; verbose: boolean; + isBuilt: boolean; - mainIPCHandle: string; - sharedIPCHandle: string; + // --- data paths + backupHome: URI; + untitledWorkspacesHome: URI; - nodeCachedDataDir?: string; + // --- misc + disableTelemetry: boolean; - installSourcePath: string; - disableUpdates: boolean; - disableCrashReporter: boolean; + serviceMachineIdResource: URI; - driverHandle?: string; - driverVerbose: boolean; + /** + * @deprecated use IRemotePathService#userHome instead (https://github.com/microsoft/vscode/issues/94506) + */ + userHome?: URI; - galleryMachineIdResource?: URI; + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: DO NOT ADD ANY OTHER PROPERTY INTO THE COLLECTION HERE + // UNLESS THIS PROPERTY IS SUPPORTED BOTH IN WEB AND DESKTOP!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index e68e0647c32..8b895e5505b 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -3,10 +3,91 @@ * 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'; + +export interface ParsedArgs { + _: string[]; + 'folder-uri'?: string[]; // undefined or array of 1 or more + 'file-uri'?: string[]; // undefined or array of 1 or more + _urls?: string[]; + help?: boolean; + version?: boolean; + telemetry?: boolean; + status?: boolean; + wait?: boolean; + waitMarkerFilePath?: string; + diff?: boolean; + add?: boolean; + goto?: boolean; + 'new-window'?: boolean; + 'unity-launch'?: boolean; // Always open a new window, except if opening the first window or opening a file or folder as part of the launch. + 'reuse-window'?: boolean; + locale?: string; + 'user-data-dir'?: string; + 'prof-startup'?: boolean; + 'prof-startup-prefix'?: string; + 'prof-append-timers'?: string; + verbose?: boolean; + trace?: boolean; + 'trace-category-filter'?: string; + 'trace-options'?: string; + log?: string; + logExtensionHostCommunication?: boolean; + 'extensions-dir'?: string; + '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 + 'inspect-extensions'?: string; + 'inspect-brk-extensions'?: string; + debugId?: string; + 'inspect-search'?: string; + 'inspect-brk-search'?: string; + 'disable-extensions'?: boolean; + 'disable-extension'?: string[]; // undefined or array of 1 or more + 'list-extensions'?: boolean; + 'show-versions'?: boolean; + 'category'?: string; + 'install-extension'?: string[]; // undefined or array of 1 or more + 'uninstall-extension'?: string[]; // undefined or array of 1 or more + 'locate-extension'?: string[]; // undefined or array of 1 or more + 'enable-proposed-api'?: string[]; // undefined or array of 1 or more + 'open-url'?: boolean; + 'skip-getting-started'?: boolean; + 'disable-restore-windows'?: boolean; + 'disable-telemetry'?: boolean; + 'export-default-configuration'?: string; + 'install-source'?: string; + 'disable-updates'?: boolean; + 'disable-crash-reporter'?: boolean; + 'skip-add-to-recently-opened'?: boolean; + 'max-memory'?: string; + 'file-write'?: boolean; + 'file-chmod'?: boolean; + 'driver'?: string; + 'driver-verbose'?: boolean; + remote?: string; + '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; + 'proxy-server'?: string; + 'proxy-bypass-list'?: string; + 'proxy-pac-url'?: string; + 'inspect'?: string; + 'inspect-brk'?: string; + 'js-flags'?: string; + 'disable-gpu'?: boolean; + 'nolazy'?: boolean; + 'force-device-scale-factor'?: string; + 'force-renderer-accessibility'?: boolean; + 'ignore-certificate-errors'?: boolean; + 'allow-insecure-localhost'?: boolean; +} /** * This code is also used by standalone cli's. Avoid adding any other dependencies. @@ -43,15 +124,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 +141,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 +150,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' }, @@ -89,8 +170,6 @@ export const OPTIONS: OptionDescriptions> = { 'driver': { type: 'string' }, 'logExtensionHostCommunication': { type: 'boolean' }, 'skip-getting-started': { type: 'boolean' }, - 'skip-release-notes': { type: 'boolean' }, - 'sticky-quickopen': { type: 'boolean' }, 'disable-restore-windows': { type: 'boolean' }, 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, @@ -119,7 +198,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/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index 55af70aeda5..11e54c68b8a 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -6,9 +6,8 @@ import * as assert from 'assert'; import { firstIndex } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; -import { ParsedArgs } from '../common/environment'; import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/node/files'; -import { parseArgs, ErrorReporter, OPTIONS } from 'vs/platform/environment/node/argv'; +import { parseArgs, ErrorReporter, OPTIONS, ParsedArgs } from 'vs/platform/environment/node/argv'; function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): ParsedArgs { const errorReporter: ErrorReporter = { diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 0428e1e888a..4008fe2109a 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEnvironmentService, ParsedArgs, IDebugParams, IExtensionHostDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, IDebugParams, IExtensionHostDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; import * as crypto from 'crypto'; import * as paths from 'vs/base/node/paths'; import * as os from 'os'; @@ -12,68 +13,41 @@ import * as resources from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; import product from 'vs/platform/product/common/product'; import { toLocalISOString } from 'vs/base/common/date'; -import { isWindows, isLinux } from 'vs/base/common/platform'; +import { isWindows, isLinux, Platform, platform } from 'vs/base/common/platform'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { URI } from 'vs/base/common/uri'; -// Read this before there's any chance it is overwritten -// Related to https://github.com/Microsoft/vscode/issues/30624 -export const xdgRuntimeDir = process.env['XDG_RUNTIME_DIR']; +export interface INativeEnvironmentService extends IEnvironmentService { + args: ParsedArgs; -function getNixIPCHandle(userDataPath: string, type: string): string { - const vscodePortable = process.env['VSCODE_PORTABLE']; + appRoot: string; + execPath: string; - if (xdgRuntimeDir && !vscodePortable) { - const scope = crypto.createHash('md5').update(userDataPath).digest('hex').substr(0, 8); - return path.join(xdgRuntimeDir, `vscode-${scope}-${product.version}-${type}.sock`); - } + appSettingsHome: URI; + userDataPath: string; + userHome: URI; + machineSettingsResource: URI; + backupWorkspacesPath: string; + nodeCachedDataDir?: string; - return path.join(userDataPath, `${product.version}-${type}.sock`); + mainIPCHandle: string; + sharedIPCHandle: string; + + installSourcePath: string; + + extensionsPath?: string; + builtinExtensionsPath: string; + + globalStorageHome: string; + workspaceStorageHome: string; + + driverHandle?: string; + driverVerbose: boolean; + + disableUpdates: boolean; } -function getWin32IPCHandle(userDataPath: string, type: string): string { - const scope = crypto.createHash('md5').update(userDataPath).digest('hex'); - - return `\\\\.\\pipe\\${scope}-${product.version}-${type}-sock`; -} - -function getIPCHandle(userDataPath: string, type: string): string { - if (isWindows) { - return getWin32IPCHandle(userDataPath, type); - } - - return getNixIPCHandle(userDataPath, type); -} - -function getCLIPath(execPath: string, appRoot: string, isBuilt: boolean): string { - - // Windows - if (isWindows) { - if (isBuilt) { - return path.join(path.dirname(execPath), 'bin', `${product.applicationName}.cmd`); - } - - return path.join(appRoot, 'scripts', 'code-cli.bat'); - } - - // Linux - if (isLinux) { - if (isBuilt) { - return path.join(path.dirname(execPath), 'bin', `${product.applicationName}`); - } - - return path.join(appRoot, 'scripts', 'code-cli.sh'); - } - - // macOS - if (isBuilt) { - return path.join(appRoot, 'bin', 'code'); - } - - return path.join(appRoot, 'scripts', 'code-cli.sh'); -} - -export class EnvironmentService implements IEnvironmentService { +export class EnvironmentService implements INativeEnvironmentService { _serviceBrand: undefined; @@ -90,7 +64,7 @@ export class EnvironmentService implements IEnvironmentService { readonly logsPath: string; @memoize - get userHome(): string { return os.homedir(); } + get userHome(): URI { return URI.file(os.homedir()); } @memoize get userDataPath(): string { @@ -112,22 +86,16 @@ export class EnvironmentService implements IEnvironmentService { get settingsResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'settings.json'); } @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'); } + get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'sync'); } @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')); } + get sync(): 'on' | 'off' { return this.args.sync === 'off' ? 'off' : 'on'; } @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'); } @@ -148,9 +116,12 @@ export class EnvironmentService implements IEnvironmentService { return URI.file(path.join(vscodePortable, 'argv.json')); } - return URI.file(path.join(this.userHome, product.dataFolderName, 'argv.json')); + return resources.joinPath(this.userHome, product.dataFolderName, 'argv.json'); } + @memoize + get snippetsHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'snippets'); } + @memoize get isExtensionDevelopment(): boolean { return !!this._args.extensionDevelopmentPath; } @@ -194,7 +165,7 @@ export class EnvironmentService implements IEnvironmentService { return path.join(vscodePortable, 'extensions'); } - return path.join(this.userHome, product.dataFolderName, 'extensions'); + return resources.joinPath(this.userHome, product.dataFolderName, 'extensions').fsPath; } @memoize @@ -239,6 +210,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 @@ -246,11 +229,7 @@ export class EnvironmentService implements IEnvironmentService { get isBuilt(): boolean { return !process.env['VSCODE_DEV']; } 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; } + get logLevel(): string | undefined { return this._args.log; } @memoize get mainIPCHandle(): string { return getIPCHandle(this.userDataPath, 'main'); } @@ -262,7 +241,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']; } @@ -270,6 +249,8 @@ export class EnvironmentService implements IEnvironmentService { get driverHandle(): string | undefined { return this._args['driver']; } get driverVerbose(): boolean { return !!this._args['driver-verbose']; } + get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; } + constructor(private _args: ParsedArgs, private _execPath: string) { if (!process.env['VSCODE_LOGS']) { const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); @@ -280,6 +261,79 @@ export class EnvironmentService implements IEnvironmentService { } } +// Read this before there's any chance it is overwritten +// Related to https://github.com/Microsoft/vscode/issues/30624 +export const xdgRuntimeDir = process.env['XDG_RUNTIME_DIR']; + +const safeIpcPathLengths: { [platform: number]: number } = { + [Platform.Linux]: 107, + [Platform.Mac]: 103 +}; + +function getNixIPCHandle(userDataPath: string, type: string): string { + const vscodePortable = process.env['VSCODE_PORTABLE']; + + let result: string; + if (xdgRuntimeDir && !vscodePortable) { + const scope = crypto.createHash('md5').update(userDataPath).digest('hex').substr(0, 8); + result = path.join(xdgRuntimeDir, `vscode-${scope}-${product.version}-${type}.sock`); + } else { + result = path.join(userDataPath, `${product.version}-${type}.sock`); + } + + const limit = safeIpcPathLengths[platform]; + if (typeof limit === 'number') { + if (result.length >= limit) { + // https://nodejs.org/api/net.html#net_identifying_paths_for_ipc_connections + console.warn(`WARNING: IPC handle "${result}" is longer than ${limit} chars, try a shorter --user-data-dir`); + } + } + + return result; +} + +function getWin32IPCHandle(userDataPath: string, type: string): string { + const scope = crypto.createHash('md5').update(userDataPath).digest('hex'); + + return `\\\\.\\pipe\\${scope}-${product.version}-${type}-sock`; +} + +function getIPCHandle(userDataPath: string, type: string): string { + if (isWindows) { + return getWin32IPCHandle(userDataPath, type); + } + + return getNixIPCHandle(userDataPath, type); +} + +function getCLIPath(execPath: string, appRoot: string, isBuilt: boolean): string { + + // Windows + if (isWindows) { + if (isBuilt) { + return path.join(path.dirname(execPath), 'bin', `${product.applicationName}.cmd`); + } + + return path.join(appRoot, 'scripts', 'code-cli.bat'); + } + + // Linux + if (isLinux) { + if (isBuilt) { + return path.join(path.dirname(execPath), 'bin', `${product.applicationName}`); + } + + return path.join(appRoot, 'scripts', 'code-cli.sh'); + } + + // macOS + if (isBuilt) { + return path.join(appRoot, 'bin', 'code'); + } + + return path.join(appRoot, 'scripts', 'code-cli.sh'); +} + export function parseExtensionHostPort(args: ParsedArgs, isBuild: boolean): IExtensionHostDebugParams { return parseDebugPort(args['inspect-extensions'], args['inspect-brk-extensions'], 5870, isBuild, args.debugId); } diff --git a/src/vs/platform/environment/node/stdin.ts b/src/vs/platform/environment/node/stdin.ts index 2cd928e2507..e870ac6e704 100644 --- a/src/vs/platform/environment/node/stdin.ts +++ b/src/vs/platform/environment/node/stdin.ts @@ -39,18 +39,20 @@ export function getStdinFilePath(): string { return paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`); } -export function readFromStdin(targetPath: string, verbose: boolean): Promise { +export async function readFromStdin(targetPath: string, verbose: boolean): Promise { + // open tmp file for writing const stdinFileStream = fs.createWriteStream(targetPath); - // Pipe into tmp file using terminals encoding - return resolveTerminalEncoding(verbose).then(async encoding => { - const iconv = await import('iconv-lite'); - if (!iconv.encodingExists(encoding)) { - console.log(`Unsupported terminal encoding: ${encoding}, falling back to UTF-8.`); - encoding = 'utf8'; - } - const converterStream = iconv.decodeStream(encoding); - process.stdin.pipe(converterStream).pipe(stdinFileStream); - }); + let encoding = await resolveTerminalEncoding(verbose); + + const iconv = await import('iconv-lite'); + if (!iconv.encodingExists(encoding)) { + console.log(`Unsupported terminal encoding: ${encoding}, falling back to UTF-8.`); + encoding = 'utf8'; + } + + // Pipe into tmp file using terminals encoding + const converterStream = iconv.decodeStream(encoding); + process.stdin.pipe(converterStream).pipe(stdinFileStream); } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index f3fb5ecca84..6029ccdc646 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,15 @@ 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); - } - } - - if (uuid) { - headers['X-Market-User-Id'] = uuid; - } - + const uuid = await getServiceMachineId(environmentService, fileService, storageService); + headers['X-Market-User-Id'] = uuid; return headers; - } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 293d0a90056..93245d43505 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IExeBasedExtensionTip } from 'vs/platform/product/common/productService'; export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$'; export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN); @@ -221,6 +222,20 @@ export interface IGlobalExtensionEnablementService { } +export type IExecutableBasedExtensionTip = { extensionId: string } & Omit, 'important'>; +export type IWorkspaceTips = { readonly remoteSet: string[]; readonly recommendations: string[]; }; + +export const IExtensionTipsService = createDecorator('IExtensionTipsService'); +export interface IExtensionTipsService { + _serviceBrand: undefined; + + getImportantExecutableBasedTips(): Promise; + getOtherExecutableBasedTips(): Promise; + getAllWorkspacesTips(): Promise; +} + + + export const ExtensionsLabel = localize('extensions', "Extensions"); export const ExtensionsChannelId = 'extensions'; export const PreferencesLabel = localize('preferences', "Preferences"); diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 035559f7ec6..40903640b2c 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; @@ -130,3 +130,23 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer return Promise.resolve(this.channel.call('getExtensionsReport')); } } + +export class ExtensionTipsChannel implements IServerChannel { + + constructor(private service: IExtensionTipsService) { + } + + listen(context: any, event: string): Event { + throw new Error('Invalid listen'); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'getImportantExecutableBasedTips': return this.service.getImportantExecutableBasedTips(); + case 'getOtherExecutableBasedTips': return this.service.getOtherExecutableBasedTips(); + case 'getAllWorkspacesTips': return this.service.getAllWorkspacesTips(); + } + + throw new Error('Invalid call'); + } +} diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index 71d3fb133f6..bad92e00651 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -116,25 +116,4 @@ export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set; - metadata: any; -} - -/** - * Parses the built-in extension JSON data and filters it down to the - * extensions built into this product quality. - */ -export function parseBuiltInExtensions(rawJson: string, productQuality: string | undefined) { - const parsed: IBuiltInExtension[] = JSON.parse(rawJson); - if (!productQuality) { - return parsed; - } - - return parsed.filter(ext => ext.forQualities?.indexOf?.(productQuality) !== -1); -} +} \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 25a4468ad4b..9b561c030f2 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -12,7 +12,7 @@ import { join } from 'vs/base/common/path'; import { Limiter } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { rimraf } from 'vs/base/node/pfs'; export class ExtensionsLifecycle extends Disposable { @@ -20,7 +20,7 @@ export class ExtensionsLifecycle extends Disposable { private processesLimiter: Limiter = new Limiter(5); // Run max 5 processes in parallel constructor( - private environmentService: IEnvironmentService, + private environmentService: INativeEnvironmentService, private logService: ILogService ) { super(); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 5b056505913..c2383fa0f40 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -21,9 +21,10 @@ import { INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion, parseBuiltInExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { Limiter, createCancelablePromise, CancelablePromise, Queue } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import * as semver from 'semver-umd'; @@ -45,7 +46,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { IProductService } from 'vs/platform/product/common/productService'; const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; @@ -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 @@ -128,12 +128,11 @@ export class ExtensionManagementService extends Disposable implements IExtension onDidUninstallExtension: Event = this._onDidUninstallExtension.event; constructor( - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @ILogService private readonly logService: ILogService, @optional(IDownloadService) private downloadService: IDownloadService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IProductService private readonly productService: IProductService, ) { super(); this.systemExtensionsPath = environmentService.builtinExtensionsPath; @@ -331,7 +330,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 +485,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 +496,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))); } @@ -755,6 +754,7 @@ export class ExtensionManagementService extends Disposable implements IExtension // Scan other system extensions during development const devSystemExtensionsPromise = this.getDevSystemExtensionsList() .then(devSystemExtensionsList => { + console.log(devSystemExtensionsList); if (devSystemExtensionsList.length) { return this.scanExtensions(this.devSystemExtensionsPath, ExtensionType.System) .then(result => { @@ -946,17 +946,8 @@ export class ExtensionManagementService extends Disposable implements IExtension return this._devSystemExtensionsPath; } - private _devSystemExtensionsFilePath: string | null = null; - private get devSystemExtensionsFilePath(): string { - if (!this._devSystemExtensionsFilePath) { - this._devSystemExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json')); - } - return this._devSystemExtensionsFilePath; - } - private getDevSystemExtensionsList(): Promise { - return pfs.readFile(this.devSystemExtensionsFilePath, 'utf8') - .then(data => parseBuiltInExtensions(data, this.productService.quality).map(ext => ext.name)); + return Promise.resolve(product.builtInExtensions ? product.builtInExtensions.map(e => e.name) : []); } private toNonCancellablePromise(promise: Promise): Promise { diff --git a/src/vs/platform/extensionManagement/node/extensionTipsService.ts b/src/vs/platform/extensionManagement/node/extensionTipsService.ts new file mode 100644 index 00000000000..83079dc162f --- /dev/null +++ b/src/vs/platform/extensionManagement/node/extensionTipsService.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { join, } from 'vs/base/common/path'; +import { IProductService, IExeBasedExtensionTip } from 'vs/platform/product/common/productService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { env as processEnv } from 'vs/base/common/process'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { isWindows } from 'vs/base/common/platform'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IStringDictionary, forEach } from 'vs/base/common/collections'; +import { IRequestService, asJson } from 'vs/platform/request/common/request'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ILogService } from 'vs/platform/log/common/log'; + +export class ExtensionTipsService implements IExtensionTipsService { + + _serviceBrand: any; + + private readonly allImportantExecutableTips: IStringDictionary = {}; + private readonly allOtherExecutableTips: IStringDictionary = {}; + + constructor( + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, + @IProductService private readonly productService: IProductService, + @IRequestService private readonly requestService: IRequestService, + @ILogService private readonly logService: ILogService, + ) { + if (this.productService.exeBasedExtensionTips) { + forEach(this.productService.exeBasedExtensionTips, ({ key, value }) => { + if (value.important) { + this.allImportantExecutableTips[key] = value; + } else { + this.allOtherExecutableTips[key] = value; + } + }); + } + } + + getAllWorkspacesTips(): Promise { + return this.fetchWorkspacesTips(); + } + + getImportantExecutableBasedTips(): Promise { + return this.getValidExecutableBasedExtensionTips(this.allImportantExecutableTips); + } + + getOtherExecutableBasedTips(): Promise { + return this.getValidExecutableBasedExtensionTips(this.allOtherExecutableTips); + } + + private async getValidExecutableBasedExtensionTips(executableTips: IStringDictionary): Promise { + const result: IExecutableBasedExtensionTip[] = []; + + const checkedExecutables: Map = new Map(); + for (const exeName of Object.keys(executableTips)) { + const extensionTip = executableTips[exeName]; + if (!isNonEmptyArray(extensionTip?.recommendations)) { + continue; + } + + const exePaths: string[] = []; + if (isWindows) { + if (extensionTip.windowsPath) { + exePaths.push(extensionTip.windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!) + .replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!) + .replace('%ProgramFiles%', processEnv['ProgramFiles']!) + .replace('%APPDATA%', processEnv['APPDATA']!) + .replace('%WINDIR%', processEnv['WINDIR']!)); + } + } else { + exePaths.push(join('/usr/local/bin', exeName)); + exePaths.push(join(this.environmentService.userHome.fsPath, exeName)); + } + + for (const exePath of exePaths) { + let exists = checkedExecutables.get(exePath); + if (exists === undefined) { + exists = await this.fileService.exists(URI.file(exePath)); + checkedExecutables.set(exePath, exists); + } + if (exists) { + extensionTip.recommendations.forEach(recommendation => result.push({ + extensionId: recommendation, + friendlyName: extensionTip.friendlyName, + exeFriendlyName: extensionTip.exeFriendlyName, + windowsPath: extensionTip.windowsPath, + })); + } + } + } + + return result; + } + + private async fetchWorkspacesTips(): Promise { + if (!this.productService.extensionsGallery?.recommendationsUrl) { + return []; + } + try { + const context = await this.requestService.request({ type: 'GET', url: this.productService.extensionsGallery?.recommendationsUrl }, CancellationToken.None); + if (context.res.statusCode !== 200) { + return []; + } + const result = await asJson<{ workspaceRecommendations?: IWorkspaceTips[] }>(context); + if (!result) { + return []; + } + return result.workspaceRecommendations || []; + } catch (error) { + this.logService.error(error); + return []; + } + } + +} diff --git a/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts b/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts index 0e464b1db02..328cfe122fc 100644 --- a/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts +++ b/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { join } from 'vs/base/common/path'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions'; import * as pfs from 'vs/base/node/pfs'; @@ -15,7 +15,7 @@ export class ExtensionsManifestCache extends Disposable { private extensionsManifestCache = join(this.environmentService.userDataPath, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE); constructor( - private readonly environmentService: IEnvironmentService, + private readonly environmentService: INativeEnvironmentService, extensionsManagementService: IExtensionManagementService ) { super(); 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 4a9c4489708..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; } @@ -123,7 +123,7 @@ export interface IExtensionContributions { views?: { [location: string]: IView[] }; colors?: IColor[]; localizations?: ILocalization[]; - readonly webviewEditors?: readonly IWebviewEditor[]; + readonly customEditors?: readonly IWebviewEditor[]; readonly codeActions?: readonly ICodeActionContribution[]; } diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index f20db0fdc87..073855d2890 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -18,7 +18,6 @@ import { isReadableStream, transform, ReadableStreamEvents, consumeReadableWithL import { Queue } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; -import { assign } from 'vs/base/common/objects'; import { createReadStream } from 'vs/platform/files/common/io'; export class FileService extends Disposable implements IFileService { @@ -55,7 +54,7 @@ export class FileService extends Disposable implements IFileService { // Forward events from provider const providerDisposables = new DisposableStore(); - providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes)))); + providerDisposables.add(provider.onDidChangeFile(changes => this._onDidFilesChange.fire(new FileChangesEvent(changes)))); providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider, scheme }))); if (typeof provider.onDidErrorOccur === 'function') { providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error)))); @@ -147,11 +146,11 @@ export class FileService extends Disposable implements IFileService { //#endregion - private _onAfterOperation: Emitter = this._register(new Emitter()); - readonly onAfterOperation: Event = this._onAfterOperation.event; + private _onDidRunOperation = this._register(new Emitter()); + readonly onDidRunOperation = this._onDidRunOperation.event; - private _onError: Emitter = this._register(new Emitter()); - readonly onError: Event = this._onError.event; + private _onError = this._register(new Emitter()); + readonly onError = this._onError.event; //#region File Metadata Resolving @@ -183,21 +182,21 @@ export class FileService extends Disposable implements IFileService { const stat = await provider.stat(resource); - let trie: TernarySearchTree | undefined; + let trie: TernarySearchTree | undefined; return this.toFileStat(provider, resource, stat, undefined, !!resolveMetadata, (stat, siblings) => { // lazy trie to check for recursive resolving if (!trie) { - trie = TernarySearchTree.forPaths(); - trie.set(resource.toString(), true); + trie = TernarySearchTree.forUris(); + trie.set(resource, true); if (isNonEmptyArray(resolveTo)) { - resolveTo.forEach(uri => trie!.set(uri.toString(), true)); + resolveTo.forEach(uri => trie!.set(uri, true)); } } // check for recursive resolving - if (Boolean(trie.findSuperstr(stat.resource.toString()) || trie.get(stat.resource.toString()))) { + if (Boolean(trie.findSuperstr(stat.resource) || trie.get(stat.resource))) { return true; } @@ -299,7 +298,7 @@ export class FileService extends Disposable implements IFileService { const fileStat = await this.writeFile(resource, bufferOrReadableOrStream); // events - this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat)); + this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat)); return fileStat; } @@ -384,14 +383,15 @@ export class FileService extends Disposable implements IFileService { async readFile(resource: URI, options?: IReadFileOptions): Promise { const provider = await this.withReadProvider(resource); - const stream = await this.doReadAsFileStream(provider, resource, assign({ + const stream = await this.doReadAsFileStream(provider, resource, { + ...options, // optimization: since we know that the caller does not // care about buffering, we indicate this to the reader. // this reduces all the overhead the buffered reading // has (open, read, close) if the provider supports // unbuffered reading. preferUnbuffered: true - }, options || Object.create(null))); + }); return { ...stream, @@ -549,7 +549,7 @@ export class FileService extends Disposable implements IFileService { // resolve and send events const fileStat = await this.resolve(target, { resolveMetadata: true }); - this._onAfterOperation.fire(new FileOperationEvent(source, mode === 'move' ? FileOperation.MOVE : FileOperation.COPY, fileStat)); + this._onDidRunOperation.fire(new FileOperationEvent(source, mode === 'move' ? FileOperation.MOVE : FileOperation.COPY, fileStat)); return fileStat; } @@ -563,7 +563,7 @@ export class FileService extends Disposable implements IFileService { // resolve and send events const fileStat = await this.resolve(target, { resolveMetadata: true }); - this._onAfterOperation.fire(new FileOperationEvent(source, mode === 'copy' ? FileOperation.COPY : FileOperation.MOVE, fileStat)); + this._onDidRunOperation.fire(new FileOperationEvent(source, mode === 'copy' ? FileOperation.COPY : FileOperation.MOVE, fileStat)); return fileStat; } @@ -717,7 +717,7 @@ export class FileService extends Disposable implements IFileService { // events const fileStat = await this.resolve(resource, { resolveMetadata: true }); - this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat)); + this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat)); return fileStat; } @@ -799,15 +799,15 @@ export class FileService extends Disposable implements IFileService { await provider.delete(resource, { recursive, useTrash }); // Events - this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE)); + this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE)); } //#endregion //#region File Watching - private _onFileChanges: Emitter = this._register(new Emitter()); - readonly onFileChanges: Event = this._onFileChanges.event; + private _onDidFilesChange = this._register(new Emitter()); + readonly onDidFilesChange = this._onDidFilesChange.event; private activeWatchers = new Map(); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 6360e6830a5..7438ec84efe 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -63,12 +63,12 @@ export interface IFileService { * Allows to listen for file changes. The event will fire for every file within the opened workspace * (if any) as well as all files that have been watched explicitly using the #watch() API. */ - readonly onFileChanges: Event; + readonly onDidFilesChange: Event; /** * An event that is fired upon successful completion of a certain file operation. */ - readonly onAfterOperation: Event; + readonly onDidRunOperation: Event; /** * Resolve the properties of a file/folder identified by the resource. @@ -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: @@ -471,15 +473,7 @@ export interface IFileChange { export class FileChangesEvent { - private readonly _changes: readonly IFileChange[]; - - constructor(changes: readonly IFileChange[]) { - this._changes = changes; - } - - get changes() { - return this._changes; - } + constructor(public readonly changes: readonly IFileChange[]) { } /** * Returns true if this change event contains the provided file with the given change type (if provided). In case of @@ -493,7 +487,7 @@ export class FileChangesEvent { const checkForChangeType = !isUndefinedOrNull(type); - return this._changes.some(change => { + return this.changes.some(change => { if (checkForChangeType && change.type !== type) { return false; } @@ -550,11 +544,11 @@ export class FileChangesEvent { } private getOfType(type: FileChangeType): IFileChange[] { - return this._changes.filter(change => change.type === type); + return this.changes.filter(change => change.type === type); } private hasType(type: FileChangeType): boolean { - return this._changes.some(change => { + return this.changes.some(change => { return change.type === type; }); } @@ -771,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/workbench/services/userData/common/inMemoryUserDataProvider.ts b/src/vs/platform/files/common/inMemoryFilesystemProvider.ts similarity index 98% rename from src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts rename to src/vs/platform/files/common/inMemoryFilesystemProvider.ts index 847aae72b9a..ad689bb57fc 100644 --- a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts +++ b/src/vs/platform/files/common/inMemoryFilesystemProvider.ts @@ -66,9 +66,7 @@ export class InMemoryFileSystemProvider extends Disposable implements IFileSyste async readdir(resource: URI): Promise<[string, FileType][]> { const entry = this._lookupAsDirectory(resource, false); let result: [string, FileType][] = []; - for (const [name, child] of entry.entries) { - result.push([name, child.type]); - } + entry.entries.forEach((child, name) => result.push([name, child.type])); return result; } 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/nodejs/watcherService.ts b/src/vs/platform/files/node/watcher/nodejs/watcherService.ts index b25613a2146..1475b56d004 100644 --- a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts +++ b/src/vs/platform/files/node/watcher/nodejs/watcherService.ts @@ -20,7 +20,7 @@ export class FileWatcher extends Disposable { constructor( private path: string, - private onFileChanges: (changes: IDiskFileChange[]) => void, + private onDidFilesChange: (changes: IDiskFileChange[]) => void, private onLogMessage: (msg: ILogMessage) => void, private verboseLogging: boolean ) { @@ -101,7 +101,7 @@ export class FileWatcher extends Disposable { // Fire if (normalizedFileChanges.length > 0) { - this.onFileChanges(normalizedFileChanges); + this.onDidFilesChange(normalizedFileChanges); } return Promise.resolve(); diff --git a/src/vs/platform/files/node/watcher/nsfw/watcherService.ts b/src/vs/platform/files/node/watcher/nsfw/watcherService.ts index 7a32b3e04b1..412bd951ba6 100644 --- a/src/vs/platform/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/watcherService.ts @@ -21,7 +21,7 @@ export class FileWatcher extends Disposable { constructor( private folders: IWatcherRequest[], - private onFileChanges: (changes: IDiskFileChange[]) => void, + private onDidFilesChange: (changes: IDiskFileChange[]) => void, private onLogMessage: (msg: ILogMessage) => void, private verboseLogging: boolean, ) { @@ -68,7 +68,7 @@ export class FileWatcher extends Disposable { this.service.setVerboseLogging(this.verboseLogging); const options = {}; - this._register(this.service.watch(options)(e => !this.isDisposed && this.onFileChanges(e))); + this._register(this.service.watch(options)(e => !this.isDisposed && this.onDidFilesChange(e))); this._register(this.service.onLogMessage(m => this.onLogMessage(m))); diff --git a/src/vs/platform/files/node/watcher/unix/watcherService.ts b/src/vs/platform/files/node/watcher/unix/watcherService.ts index 5fd8d4e39aa..ea29e3fcdbc 100644 --- a/src/vs/platform/files/node/watcher/unix/watcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/watcherService.ts @@ -20,7 +20,7 @@ export class FileWatcher extends Disposable { constructor( private folders: IWatcherRequest[], - private onFileChanges: (changes: IDiskFileChange[]) => void, + private onDidFilesChange: (changes: IDiskFileChange[]) => void, private onLogMessage: (msg: ILogMessage) => void, private verboseLogging: boolean, private watcherOptions: IWatcherOptions = {} @@ -67,7 +67,7 @@ export class FileWatcher extends Disposable { this.service.setVerboseLogging(this.verboseLogging); - this._register(this.service.watch(this.watcherOptions)(e => !this.isDisposed && this.onFileChanges(e))); + this._register(this.service.watch(this.watcherOptions)(e => !this.isDisposed && this.onDidFilesChange(e))); this._register(this.service.onLogMessage(m => this.onLogMessage(m))); diff --git a/src/vs/platform/files/node/watcher/win32/watcherService.ts b/src/vs/platform/files/node/watcher/win32/watcherService.ts index c8f0d8fdfb5..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 { @@ -16,13 +16,13 @@ export class FileWatcher implements IDisposable { constructor( folders: { path: string, excludes: string[] }[], - private onFileChanges: (changes: IDiskFileChange[]) => void, + private onDidFilesChange: (changes: IDiskFileChange[]) => void, private onLogMessage: (msg: ILogMessage) => void, private verboseLogging: boolean ) { 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 ("/"). @@ -62,7 +62,7 @@ export class FileWatcher implements IDisposable { // Emit through event emitter if (events.length > 0) { - this.onFileChanges(events); + this.onDidFilesChange(events); } } @@ -72,4 +72,4 @@ export class FileWatcher implements IDisposable { this.service = undefined; } } -} \ No newline at end of file +} diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts similarity index 96% rename from src/vs/platform/files/test/node/diskFileService.test.ts rename to src/vs/platform/files/test/electron-browser/diskFileService.test.ts index ad0f3d22529..12bcc8428be 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { tmpdir } from 'os'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; -import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { generateUuid } from 'vs/base/common/uuid'; import { join, basename, dirname, posix } from 'vs/base/common/path'; @@ -67,6 +67,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.FileReadStream | + FileSystemProviderCapabilities.Trash | FileSystemProviderCapabilities.FileFolderCopy; if (isLinux) { @@ -131,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(); @@ -165,7 +168,7 @@ suite('Disk File Service', function () { test('createFolder', async () => { let event: FileOperationEvent | undefined; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const parent = await service.resolve(URI.file(testDir)); @@ -185,7 +188,7 @@ suite('Disk File Service', function () { test('createFolder: creating multiple folders at once', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const multiFolderPaths = ['a', 'couple', 'of', 'folders']; const parent = await service.resolve(URI.file(testDir)); @@ -459,13 +462,21 @@ suite('Disk File Service', function () { }); test('deleteFile', async () => { + return testDeleteFile(false); + }); + + (isLinux /* trash is unreliable on Linux */ ? test.skip : test)('deleteFile (useTrash)', async () => { + return testDeleteFile(true); + }); + + async function testDeleteFile(useTrash: boolean): Promise { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const resource = URI.file(join(testDir, 'deep', 'conway.js')); const source = await service.resolve(resource); - await service.del(source.resource); + await service.del(source.resource, { useTrash }); assert.equal(existsSync(source.resource.fsPath), false); @@ -475,14 +486,14 @@ suite('Disk File Service', function () { let error: Error | undefined = undefined; try { - await service.del(source.resource); + await service.del(source.resource, { useTrash }); } catch (e) { error = e; } assert.ok(error); assert.equal((error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND); - }); + } test('deleteFile - symbolic link (exists)', async () => { if (isWindows) { @@ -496,7 +507,7 @@ suite('Disk File Service', function () { const source = await service.resolve(link); let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); await service.del(source.resource); @@ -519,7 +530,7 @@ suite('Disk File Service', function () { await symlink(target.fsPath, link.fsPath); let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); await service.del(link); @@ -531,19 +542,27 @@ suite('Disk File Service', function () { }); test('deleteFolder (recursive)', async () => { + return testDeleteFolderRecursive(false); + }); + + (isLinux /* trash is unreliable on Linux */ ? test.skip : test)('deleteFolder (recursive, useTrash)', async () => { + return testDeleteFolderRecursive(true); + }); + + async function testDeleteFolderRecursive(useTrash: boolean): Promise { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const resource = URI.file(join(testDir, 'deep')); const source = await service.resolve(resource); - await service.del(source.resource, { recursive: true }); + await service.del(source.resource, { recursive: true, useTrash }); assert.equal(existsSync(source.resource.fsPath), false); assert.ok(event!); assert.equal(event!.resource.fsPath, resource.fsPath); assert.equal(event!.operation, FileOperation.DELETE); - }); + } test('deleteFolder (non recursive)', async () => { const resource = URI.file(join(testDir, 'deep')); @@ -561,7 +580,7 @@ suite('Disk File Service', function () { test('move', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = URI.file(join(testDir, 'index.html')); const sourceContents = readFileSync(source.fsPath); @@ -641,7 +660,7 @@ suite('Disk File Service', function () { async function testMoveAcrossProviders(sourceFile = 'index.html'): Promise { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = URI.file(join(testDir, sourceFile)); const sourceContents = readFileSync(source.fsPath); @@ -665,7 +684,7 @@ suite('Disk File Service', function () { test('move - multi folder', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const multiFolderPaths = ['a', 'couple', 'of', 'folders']; const renameToPath = join(...multiFolderPaths, 'other.html'); @@ -684,7 +703,7 @@ suite('Disk File Service', function () { test('move - directory', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = URI.file(join(testDir, 'deep')); @@ -728,7 +747,7 @@ suite('Disk File Service', function () { async function testMoveFolderAcrossProviders(): Promise { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = URI.file(join(testDir, 'deep')); const sourceChildren = readdirSync(source.fsPath); @@ -753,7 +772,7 @@ suite('Disk File Service', function () { test('move - MIX CASE', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); assert.ok(source.size > 0); @@ -774,7 +793,7 @@ suite('Disk File Service', function () { test('move - same file', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); assert.ok(source.size > 0); @@ -794,7 +813,7 @@ suite('Disk File Service', function () { test('move - same file #2', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); assert.ok(source.size > 0); @@ -817,7 +836,7 @@ suite('Disk File Service', function () { test('move - source parent of target', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); let source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); const originalSize = source.size; @@ -839,7 +858,7 @@ suite('Disk File Service', function () { test('move - FILE_MOVE_CONFLICT', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); let source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); const originalSize = source.size; @@ -863,7 +882,7 @@ suite('Disk File Service', function () { let createEvent: FileOperationEvent; let moveEvent: FileOperationEvent; let deleteEvent: FileOperationEvent; - disposables.add(service.onAfterOperation(e => { + disposables.add(service.onDidRunOperation(e => { if (e.operation === FileOperation.CREATE) { createEvent = e; } else if (e.operation === FileOperation.DELETE) { @@ -927,7 +946,7 @@ suite('Disk File Service', function () { async function doTestCopy(sourceName: string = 'index.html') { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = await service.resolve(URI.file(join(testDir, sourceName))); const target = URI.file(join(testDir, 'other.html')); @@ -952,7 +971,7 @@ suite('Disk File Service', function () { let createEvent: FileOperationEvent; let copyEvent: FileOperationEvent; let deleteEvent: FileOperationEvent; - disposables.add(service.onAfterOperation(e => { + disposables.add(service.onDidRunOperation(e => { if (e.operation === FileOperation.CREATE) { createEvent = e; } else if (e.operation === FileOperation.DELETE) { @@ -1057,7 +1076,7 @@ suite('Disk File Service', function () { test('copy - same file', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); assert.ok(source.size > 0); @@ -1077,7 +1096,7 @@ suite('Disk File Service', function () { test('copy - same file #2', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const source = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); assert.ok(source.size > 0); @@ -1390,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')); @@ -1567,7 +1604,7 @@ suite('Disk File Service', function () { async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const contents = 'Hello World'; const resource = URI.file(join(testDir, 'test.txt')); @@ -1600,7 +1637,7 @@ suite('Disk File Service', function () { test('createFile (allows to overwrite existing)', async () => { let event: FileOperationEvent; - disposables.add(service.onAfterOperation(e => event = e)); + disposables.add(service.onDidRunOperation(e => event = e)); const contents = 'Hello World'; const resource = URI.file(join(testDir, 'test.txt')); @@ -2152,7 +2189,7 @@ suite('Disk File Service', function () { return event.changes.map(change => `Change: type ${toString(change.type)} path ${change.resource.toString()}`).join('\n'); } - const listenerDisposable = service.onFileChanges(event => { + const listenerDisposable = service.onDidFilesChange(event => { watcherDisposable.dispose(); listenerDisposable.dispose(); diff --git a/src/vs/platform/files/test/node/fixtures/resolver/examples/company.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/company.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/examples/company.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/company.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/examples/conway.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/conway.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/examples/conway.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/conway.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/examples/employee.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/employee.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/examples/employee.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/employee.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/examples/small.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/small.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/examples/small.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/small.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/index.html b/src/vs/platform/files/test/electron-browser/fixtures/resolver/index.html similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/index.html rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/index.html diff --git a/src/vs/platform/files/test/node/fixtures/resolver/other/deep/company.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/company.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/other/deep/company.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/company.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/other/deep/conway.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/conway.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/other/deep/conway.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/conway.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/other/deep/employee.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/employee.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/other/deep/employee.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/employee.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/other/deep/small.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/small.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/other/deep/small.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/small.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/site.css b/src/vs/platform/files/test/electron-browser/fixtures/resolver/site.css similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/site.css rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/site.css diff --git a/src/vs/platform/files/test/node/fixtures/service/binary.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/binary.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/binary.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/binary.txt diff --git a/src/vs/platform/files/test/node/fixtures/service/deep/company.js b/src/vs/platform/files/test/electron-browser/fixtures/service/deep/company.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/deep/company.js rename to src/vs/platform/files/test/electron-browser/fixtures/service/deep/company.js diff --git a/src/vs/platform/files/test/node/fixtures/service/deep/conway.js b/src/vs/platform/files/test/electron-browser/fixtures/service/deep/conway.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/deep/conway.js rename to src/vs/platform/files/test/electron-browser/fixtures/service/deep/conway.js diff --git a/src/vs/platform/files/test/node/fixtures/service/deep/employee.js b/src/vs/platform/files/test/electron-browser/fixtures/service/deep/employee.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/deep/employee.js rename to src/vs/platform/files/test/electron-browser/fixtures/service/deep/employee.js diff --git a/src/vs/platform/files/test/node/fixtures/service/deep/small.js b/src/vs/platform/files/test/electron-browser/fixtures/service/deep/small.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/deep/small.js rename to src/vs/platform/files/test/electron-browser/fixtures/service/deep/small.js diff --git a/src/vs/platform/files/test/node/fixtures/service/index.html b/src/vs/platform/files/test/electron-browser/fixtures/service/index.html similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/index.html rename to src/vs/platform/files/test/electron-browser/fixtures/service/index.html diff --git a/src/vs/platform/files/test/node/fixtures/service/lorem.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/lorem.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/lorem.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/lorem.txt diff --git a/src/vs/platform/files/test/node/fixtures/service/small.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/small.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/small.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/small.txt diff --git a/src/vs/platform/files/test/node/fixtures/service/small_umlaut.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/small_umlaut.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/small_umlaut.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/small_umlaut.txt diff --git a/src/vs/platform/files/test/node/fixtures/service/some_utf16le.css b/src/vs/platform/files/test/electron-browser/fixtures/service/some_utf16le.css similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/some_utf16le.css rename to src/vs/platform/files/test/electron-browser/fixtures/service/some_utf16le.css diff --git a/src/vs/platform/files/test/node/fixtures/service/some_utf8_bom.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/some_utf8_bom.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/some_utf8_bom.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/some_utf8_bom.txt diff --git a/src/vs/platform/files/test/node/normalizer.test.ts b/src/vs/platform/files/test/electron-browser/normalizer.test.ts similarity index 94% rename from src/vs/platform/files/test/node/normalizer.test.ts rename to src/vs/platform/files/test/electron-browser/normalizer.test.ts index 3f14bb70246..aade9f0879e 100644 --- a/src/vs/platform/files/test/node/normalizer.test.ts +++ b/src/vs/platform/files/test/electron-browser/normalizer.test.ts @@ -15,14 +15,14 @@ function toFileChangesEvent(changes: IDiskFileChange[]): FileChangesEvent { } class TestFileWatcher { - private readonly _onFileChanges: Emitter; + private readonly _onDidFilesChange: Emitter; constructor() { - this._onFileChanges = new Emitter(); + this._onDidFilesChange = new Emitter(); } - get onFileChanges(): Event { - return this._onFileChanges.event; + get onDidFilesChange(): Event { + return this._onDidFilesChange.event; } report(changes: IDiskFileChange[]): void { @@ -36,7 +36,7 @@ class TestFileWatcher { // Emit through event emitter if (normalizedEvents.length > 0) { - this._onFileChanges.fire(toFileChangesEvent(normalizedEvents)); + this._onDidFilesChange.fire(toFileChangesEvent(normalizedEvents)); } } } @@ -62,7 +62,7 @@ suite('Normalizer', () => { { path: deleted.fsPath, type: FileChangeType.DELETED }, ]; - watch.onFileChanges(e => { + watch.onDidFilesChange(e => { assert.ok(e); assert.equal(e.changes.length, 3); assert.ok(e.contains(added, FileChangeType.ADDED)); @@ -101,7 +101,7 @@ suite('Normalizer', () => { { path: updatedFile.fsPath, type: FileChangeType.UPDATED } ]; - watch.onFileChanges(e => { + watch.onDidFilesChange(e => { assert.ok(e); assert.equal(e.changes.length, 5); @@ -131,7 +131,7 @@ suite('Normalizer', () => { { path: unrelated.fsPath, type: FileChangeType.UPDATED }, ]; - watch.onFileChanges(e => { + watch.onDidFilesChange(e => { assert.ok(e); assert.equal(e.changes.length, 1); @@ -156,7 +156,7 @@ suite('Normalizer', () => { { path: unrelated.fsPath, type: FileChangeType.UPDATED }, ]; - watch.onFileChanges(e => { + watch.onDidFilesChange(e => { assert.ok(e); assert.equal(e.changes.length, 2); @@ -182,7 +182,7 @@ suite('Normalizer', () => { { path: unrelated.fsPath, type: FileChangeType.UPDATED }, ]; - watch.onFileChanges(e => { + watch.onDidFilesChange(e => { assert.ok(e); assert.equal(e.changes.length, 2); @@ -211,7 +211,7 @@ suite('Normalizer', () => { { path: updated.fsPath, type: FileChangeType.DELETED } ]; - watch.onFileChanges(e => { + watch.onDidFilesChange(e => { assert.ok(e); assert.equal(e.changes.length, 2); diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 10ff5a25051..6d72a93db53 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -29,7 +29,7 @@ export interface IConstructorSignature0 { } export interface IConstructorSignature1 { - new(first: A1, ...services: BrandedService[]): T; + new (first: A1, ...services: Services): T; } export interface IConstructorSignature2 { @@ -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 0b010882458..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++ > 150) { + 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/instantiation/test/common/instantiationServiceMock.ts b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts index d0a564b7edb..c1bcc6fda5a 100644 --- a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts +++ b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts @@ -13,6 +13,8 @@ interface IServiceMock { service: any; } +const isSinonSpyLike = (fn: Function): fn is sinon.SinonSpy => fn && 'callCount' in fn; + export class TestInstantiationService extends InstantiationService { private _servciesMap: Map, any>; @@ -37,10 +39,10 @@ export class TestInstantiationService extends InstantiationService { public stub(service: ServiceIdentifier, ctor: Function): T; public stub(service: ServiceIdentifier, obj: Partial): T; - public stub(service: ServiceIdentifier, ctor: Function, property: string, value: any): sinon.SinonStub; - public stub(service: ServiceIdentifier, obj: Partial, property: string, value: any): sinon.SinonStub; - public stub(service: ServiceIdentifier, property: string, value: any): sinon.SinonStub; - public stub(serviceIdentifier: ServiceIdentifier, arg2: any, arg3?: string, arg4?: any): sinon.SinonStub { + public stub(service: ServiceIdentifier, ctor: Function, property: string, value: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub; + public stub(service: ServiceIdentifier, obj: Partial, property: string, value: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub; + public stub(service: ServiceIdentifier, property: string, value: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub; + public stub(serviceIdentifier: ServiceIdentifier, arg2: any, arg3?: string, arg4?: any): sinon.SinonStub | sinon.SinonSpy { let service = typeof arg2 !== 'string' ? arg2 : undefined; let serviceMock: IServiceMock = { id: serviceIdentifier, service: service }; let property = typeof arg2 === 'string' ? arg2 : arg3; @@ -53,9 +55,11 @@ export class TestInstantiationService extends InstantiationService { stubObject[property].restore(); } if (typeof value === 'function') { - stubObject[property] = value; + const spy = isSinonSpyLike(value) ? value : sinon.spy(value); + stubObject[property] = spy; + return spy; } else { - let stub = value ? sinon.stub().returns(value) : sinon.stub(); + const stub = value ? sinon.stub().returns(value) : sinon.stub(); stubObject[property] = stub; return stub; } @@ -67,9 +71,9 @@ export class TestInstantiationService extends InstantiationService { } public stubPromise(service?: ServiceIdentifier, fnProperty?: string, value?: any): T | sinon.SinonStub; - public stubPromise(service?: ServiceIdentifier, ctor?: any, fnProperty?: string, value?: any): sinon.SinonStub; - public stubPromise(service?: ServiceIdentifier, obj?: any, fnProperty?: string, value?: any): sinon.SinonStub; - public stubPromise(arg1?: any, arg2?: any, arg3?: any, arg4?: any): sinon.SinonStub { + public stubPromise(service?: ServiceIdentifier, ctor?: any, fnProperty?: string, value?: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub; + public stubPromise(service?: ServiceIdentifier, obj?: any, fnProperty?: string, value?: V): V extends Function ? sinon.SinonSpy : sinon.SinonStub; + public stubPromise(arg1?: any, arg2?: any, arg3?: any, arg4?: any): sinon.SinonStub | sinon.SinonSpy { arg3 = typeof arg2 === 'string' ? Promise.resolve(arg3) : arg3; arg4 = typeof arg2 !== 'string' && typeof arg3 === 'string' ? Promise.resolve(arg4) : arg4; return this.stub(arg1, arg2, arg3, arg4); @@ -124,4 +128,4 @@ export class TestInstantiationService extends InstantiationService { interface SinonOptions { mock?: boolean; stub?: boolean; -} \ No newline at end of file +} diff --git a/src/vs/code/common/issue/issueReporterUtil.ts b/src/vs/platform/issue/common/issueReporterUtil.ts similarity index 100% rename from src/vs/code/common/issue/issueReporterUtil.ts rename to src/vs/platform/issue/common/issueReporterUtil.ts diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 1307534e24a..193ab70b12f 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -12,6 +12,7 @@ import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainS import { PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowState } from 'vs/platform/windows/electron-main/windows'; @@ -30,7 +31,7 @@ export class IssueMainService implements IIssueService { constructor( private machineId: string, private userEnv: IProcessEnvironment, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @ILaunchMainService private readonly launchMainService: ILaunchMainService, @ILogService private readonly logService: ILogService, @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, diff --git a/src/vs/platform/issue/node/issue.ts b/src/vs/platform/issue/node/issue.ts index aeb55398adf..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; @@ -59,6 +60,8 @@ export interface IssueReporterData extends WindowData { enabledExtensions: IssueReporterExtensionData[]; issueType?: IssueType; extensionId?: string; + readonly issueTitle?: string; + readonly issueBody?: string; } export interface ISettingSearchResult { diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 6440a346f59..1fce530772a 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -35,6 +35,10 @@ export abstract class AbstractKeybindingService extends Disposable implements IK private _currentChordChecker: IntervalTimer; private _currentChordStatusMessage: IDisposable | null; + public get inChordMode(): boolean { + return !!this._currentChord; + } + constructor( private _contextKeyService: IContextKeyService, protected _commandService: ICommandService, diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index e11bd846c43..17ab82b8184 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -50,6 +50,8 @@ export const IKeybindingService = createDecorator('keybindin export interface IKeybindingService { _serviceBrand: undefined; + readonly inChordMode: boolean; + onDidUpdateKeybindings: Event; /** diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 2ae709887b3..951d01f4c16 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -6,12 +6,15 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContext, ContextKeyOrExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IContext, ContextKeyExpression, ContextKeyExprType } from 'vs/platform/contextkey/common/contextkey'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { keys } from 'vs/base/common/map'; export interface IResolveResult { + /** Whether the resolved keybinding is entering a chord */ enterChord: boolean; + /** Whether the resolved keybinding is leaving (and executing) a chord */ + leaveChord: boolean; commandId: string | null; commandArgs: any; bubble: boolean; @@ -46,12 +49,17 @@ export class KeybindingResolver { continue; } + if (k.when && k.when.type === ContextKeyExprType.False) { + // when condition is false + continue; + } + // TODO@chords this._addKeyPress(k.keypressParts[0], k); } } - private static _isTargetedForRemoval(defaultKb: ResolvedKeybindingItem, keypressFirstPart: string | null, keypressChordPart: string | null, command: string, when: ContextKeyExpr | undefined): boolean { + private static _isTargetedForRemoval(defaultKb: ResolvedKeybindingItem, keypressFirstPart: string | null, keypressChordPart: string | null, command: string, when: ContextKeyExpression | undefined): boolean { if (defaultKb.command !== command) { return false; } @@ -172,7 +180,7 @@ export class KeybindingResolver { /** * Returns true if it is provable `a` implies `b`. */ - public static whenIsEntirelyIncluded(a: ContextKeyExpr | null | undefined, b: ContextKeyExpr | null | undefined): boolean { + public static whenIsEntirelyIncluded(a: ContextKeyExpression | null | undefined, b: ContextKeyExpression | null | undefined): boolean { if (!b) { return true; } @@ -186,11 +194,11 @@ export class KeybindingResolver { /** * Returns true if it is provable `p` implies `q`. */ - private static _implies(p: ContextKeyExpr, q: ContextKeyExpr): boolean { + private static _implies(p: ContextKeyExpression, q: ContextKeyExpression): boolean { const notP = p.negate(); - const terminals = (node: ContextKeyExpr) => { - if (node instanceof ContextKeyOrExpr) { + const terminals = (node: ContextKeyExpression) => { + if (node.type === ContextKeyExprType.Or) { return node.expr; } return [node]; @@ -285,6 +293,7 @@ export class KeybindingResolver { if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) { return { enterChord: true, + leaveChord: false, commandId: null, commandArgs: null, bubble: false @@ -293,6 +302,7 @@ export class KeybindingResolver { return { enterChord: false, + leaveChord: result.keypressParts.length > 1, commandId: result.command, commandArgs: result.commandArgs, bubble: result.bubble @@ -313,7 +323,7 @@ export class KeybindingResolver { return null; } - public static contextMatchesRules(context: IContext, rules: ContextKeyExpr | null | undefined): boolean { + public static contextMatchesRules(context: IContext, rules: ContextKeyExpression | null | undefined): boolean { if (!rules) { return true; } diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 5c3de02794b..6f249867f8e 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -6,14 +6,14 @@ import { KeyCode, Keybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { CommandsRegistry, ICommandHandler, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; export interface IKeybindingItem { keybinding: Keybinding; command: string; commandArgs?: any; - when: ContextKeyExpr | null | undefined; + when: ContextKeyExpression | null | undefined; weight1: number; weight2: number; } @@ -39,7 +39,7 @@ export interface IKeybindingRule extends IKeybindings { id: string; weight: number; args?: any; - when: ContextKeyExpr | null | undefined; + when: ContextKeyExpression | null | undefined; } export interface IKeybindingRule2 { @@ -50,7 +50,7 @@ export interface IKeybindingRule2 { id: string; args?: any; weight: number; - when: ContextKeyExpr | undefined; + when: ContextKeyExpression | undefined; } export const enum KeybindingWeight { @@ -209,7 +209,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { } } - private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, commandArgs: any, weight1: number, weight2: number, when: ContextKeyExpr | null | undefined): void { + private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, commandArgs: any, weight1: number, weight2: number, when: ContextKeyExpression | null | undefined): void { if (OS === OperatingSystem.Windows) { this._assertNoCtrlAlt(keybinding.parts[0], commandId); } diff --git a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts index 00b05e05213..a32518446e7 100644 --- a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts +++ b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts @@ -5,7 +5,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class ResolvedKeybindingItem { _resolvedKeybindingItemBrand: void; @@ -15,10 +15,10 @@ export class ResolvedKeybindingItem { public readonly bubble: boolean; public readonly command: string | null; public readonly commandArgs: any; - public readonly when: ContextKeyExpr | undefined; + public readonly when: ContextKeyExpression | undefined; public readonly isDefault: boolean; - constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpr | undefined, isDefault: boolean) { + constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean) { this.resolvedKeybinding = resolvedKeybinding; this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : []; this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false); diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 05a93605d42..286ea8a57ae 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -7,7 +7,7 @@ import { KeyChord, KeyCode, KeyMod, Keybinding, ResolvedKeybinding, SimpleKeybin import { OS } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContext, IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContext, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; @@ -182,7 +182,7 @@ suite('AbstractKeybindingService', () => { statusMessageCallsDisposed = null; }); - function kbItem(keybinding: number, command: string, when?: ContextKeyExpr): ResolvedKeybindingItem { + function kbItem(keybinding: number, command: string, when?: ContextKeyExpression): ResolvedKeybindingItem { const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : undefined); return new ResolvedKeybindingItem( resolvedKeybinding, diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index c85003be1ac..7a71aad81d6 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; -import { ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContext, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -20,7 +20,7 @@ function createContext(ctx: any) { suite('KeybindingResolver', () => { - function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr | undefined, isDefault: boolean): ResolvedKeybindingItem { + function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean): ResolvedKeybindingItem { const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : undefined); return new ResolvedKeybindingItem( resolvedKeybinding, @@ -225,7 +225,7 @@ suite('KeybindingResolver', () => { test('resolve command', function () { - function _kbItem(keybinding: number, command: string, when: ContextKeyExpr | undefined): ResolvedKeybindingItem { + function _kbItem(keybinding: number, command: string, when: ContextKeyExpression | undefined): ResolvedKeybindingItem { return kbItem(keybinding, command, null, when, true); } diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index c2062a4e4b4..998169968bd 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; -import { ContextKeyExpr, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -47,7 +47,7 @@ export class MockContextKeyService implements IContextKeyService { this._keys.set(key, ret); return ret; } - public contextMatchesRules(rules: ContextKeyExpr): boolean { + public contextMatchesRules(rules: ContextKeyExpression): boolean { return false; } public get onDidChangeContext(): Event { @@ -71,6 +71,8 @@ export class MockContextKeyService implements IContextKeyService { export class MockKeybindingService implements IKeybindingService { public _serviceBrand: undefined; + public readonly inChordMode: boolean = false; + public get onDidUpdateKeybindings(): Event { return Event.None; } 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/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 9d52a59e7b8..17698833e34 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -6,9 +6,11 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURLService } from 'vs/platform/url/common/url'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; -import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { OpenContext, IWindowSettings } from 'vs/platform/windows/common/windows'; +import { IWindowSettings } from 'vs/platform/windows/common/windows'; +import { OpenContext } from 'vs/platform/windows/node/window'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { whenDeleted } from 'vs/base/node/pfs'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; 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/lifecycle/common/lifecycleService.ts b/src/vs/platform/lifecycle/common/lifecycleService.ts index 1b2cb90c8af..d0fad7f28f4 100644 --- a/src/vs/platform/lifecycle/common/lifecycleService.ts +++ b/src/vs/platform/lifecycle/common/lifecycleService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Barrier } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILifecycleService, BeforeShutdownEvent, WillShutdownEvent, StartupKind, LifecyclePhase, LifecyclePhaseToString } from 'vs/platform/lifecycle/common/lifecycle'; @@ -15,13 +15,13 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi _serviceBrand: undefined; protected readonly _onBeforeShutdown = this._register(new Emitter()); - readonly onBeforeShutdown: Event = this._onBeforeShutdown.event; + readonly onBeforeShutdown = this._onBeforeShutdown.event; protected readonly _onWillShutdown = this._register(new Emitter()); - readonly onWillShutdown: Event = this._onWillShutdown.event; + readonly onWillShutdown = this._onWillShutdown.event; protected readonly _onShutdown = this._register(new Emitter()); - readonly onShutdown: Event = this._onShutdown.event; + readonly onShutdown = this._onShutdown.event; protected _startupKind: StartupKind = StartupKind.NewWindow; get startupKind(): StartupKind { return this._startupKind; } @@ -29,7 +29,7 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi private _phase: LifecyclePhase = LifecyclePhase.Starting; get phase(): LifecyclePhase { return this._phase; } - private phaseWhen = new Map(); + private readonly phaseWhen = new Map(); constructor( @ILogService protected readonly logService: ILogService diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 35366e53cec..a2ae8cd766b 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -13,7 +13,7 @@ import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { Barrier, timeout } from 'vs/base/common/async'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; export const ILifecycleMainService = createDecorator('lifecycleMainService'); @@ -141,7 +141,28 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted - private windowToCloseRequest: Set = new Set(); + private readonly _onBeforeShutdown = this._register(new Emitter()); + readonly onBeforeShutdown = this._onBeforeShutdown.event; + + private readonly _onWillShutdown = this._register(new Emitter()); + readonly onWillShutdown = this._onWillShutdown.event; + + private readonly _onBeforeWindowClose = this._register(new Emitter()); + readonly onBeforeWindowClose = this._onBeforeWindowClose.event; + + private readonly _onBeforeWindowUnload = this._register(new Emitter()); + readonly onBeforeWindowUnload = this._onBeforeWindowUnload.event; + + private _quitRequested = false; + get quitRequested(): boolean { return this._quitRequested; } + + private _wasRestarted: boolean = false; + get wasRestarted(): boolean { return this._wasRestarted; } + + private _phase = LifecycleMainPhase.Starting; + get phase(): LifecycleMainPhase { return this._phase; } + + private readonly windowToCloseRequest = new Set(); private oneTimeListenerTokenGenerator = 0; private windowCounter = 0; @@ -150,28 +171,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe private pendingWillShutdownPromise: Promise | null = null; - private _quitRequested = false; - get quitRequested(): boolean { return this._quitRequested; } - - private _wasRestarted: boolean = false; - get wasRestarted(): boolean { return this._wasRestarted; } - - private readonly _onBeforeShutdown = this._register(new Emitter()); - readonly onBeforeShutdown: Event = this._onBeforeShutdown.event; - - private readonly _onWillShutdown = this._register(new Emitter()); - readonly onWillShutdown: Event = this._onWillShutdown.event; - - private readonly _onBeforeWindowClose = this._register(new Emitter()); - readonly onBeforeWindowClose: Event = this._onBeforeWindowClose.event; - - private readonly _onBeforeWindowUnload = this._register(new Emitter()); - readonly onBeforeWindowUnload: Event = this._onBeforeWindowUnload.event; - - private _phase: LifecycleMainPhase = LifecycleMainPhase.Starting; - get phase(): LifecycleMainPhase { return this._phase; } - - private phaseWhen = new Map(); + private readonly phaseWhen = new Map(); constructor( @ILogService private readonly logService: ILogService, diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 73f5282c138..a1d4092e79c 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -5,34 +5,29 @@ import { createStyleSheet } from 'vs/base/browser/dom'; import { IListMouseEvent, IListTouchEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -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 { IPagedRenderer, PagedList, IPagedListOptions } from 'vs/base/browser/ui/list/listPaging'; +import { DefaultStyleController, IListOptions, IMultipleSelectionController, IOpenController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List, IListAccessibilityProvider, IListOptionsUpdate } 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 { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions, ICompressibleObjectTreeOptionsUpdate } from 'vs/base/browser/ui/tree/objectTree'; +import { ITreeRenderer, IAsyncDataSource, IDataSource } from 'vs/base/browser/ui/tree/tree'; +import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions, IAsyncDataTreeOptionsUpdate } 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 { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides, IAbstractTreeOptionsUpdate } 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'); @@ -235,10 +230,14 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic return [result, disposables]; } -export interface IWorkbenchListOptions extends IListOptions { +export interface IWorkbenchListOptionsUpdate extends IListOptionsUpdate { readonly overrideStyles?: IColorMapping; } +export interface IWorkbenchListOptions extends IWorkbenchListOptionsUpdate, IListOptions { + readonly accessibilityProvider: IListAccessibilityProvider; +} + export class WorkbenchList extends List { readonly contextKeyService: IContextKeyService; @@ -270,7 +269,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 +297,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 +305,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(); @@ -316,7 +315,7 @@ export class WorkbenchList extends List { this.registerListeners(); } - updateOptions(options: IWorkbenchListOptions): void { + updateOptions(options: IWorkbenchListOptionsUpdate): void { super.updateOptions(options); if (options.overrideStyles) { @@ -352,6 +351,10 @@ export class WorkbenchList extends List { } } +export interface IWorkbenchPagedListOptions extends IWorkbenchListOptionsUpdate, IPagedListOptions { + readonly accessibilityProvider: IListAccessibilityProvider; +} + export class WorkbenchPagedList extends PagedList { readonly contextKeyService: IContextKeyService; @@ -366,7 +369,7 @@ export class WorkbenchPagedList extends PagedList { container: HTMLElement, delegate: IListVirtualDelegate, renderers: IPagedRenderer[], - options: IWorkbenchListOptions, + options: IWorkbenchPagedListOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -378,7 +381,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 +427,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 +445,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,75 +462,77 @@ export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: b return e; } -export class TreeResourceNavigator extends Disposable { +abstract class ResourceNavigator extends Disposable { - private options: ITreeResourceNavigatorOptions; + 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(); - this.options = { - ...{ - openOnSelection: true - }, - ...(options || {}) - }; + this.options = { openOnSelection: true, ...(options || {}) }; this.registerListeners(); } 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,12 +544,24 @@ export class TreeResourceNavigator extends Disposable { revealIfVisible: true }, sideBySide, - element: this.tree.getSelection()[0], + element: this.treeOrList.getSelection()[0], browserEvent }); } } +export class ListResourceNavigator extends ResourceNavigator { + constructor(list: WorkbenchList | WorkbenchPagedList, options?: IResourceNavigatorOptions) { + super(list, options); + } +} + +export class TreeResourceNavigator extends ResourceNavigator { + constructor(tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, options?: IResourceNavigatorOptions) { + super(tree, { openOnSingleClick: tree.openOnSingleClick, ...(options || {}) }); + } +} + function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingService: IKeybindingService): IKeyboardNavigationEventFilter { let inChord = false; @@ -742,6 +584,7 @@ function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingS } export interface IWorkbenchObjectTreeOptions extends IObjectTreeOptions { + readonly accessibilityProvider: IListAccessibilityProvider; readonly overrideStyles?: IColorMapping; } @@ -772,10 +615,14 @@ export class WorkbenchObjectTree, TFilterData = void> } } -export interface IWorkbenchCompressibleObjectTreeOptions extends ICompressibleObjectTreeOptions { +export interface IWorkbenchCompressibleObjectTreeOptionsUpdate extends ICompressibleObjectTreeOptionsUpdate { readonly overrideStyles?: IColorMapping; } +export interface IWorkbenchCompressibleObjectTreeOptions extends IWorkbenchCompressibleObjectTreeOptionsUpdate, ICompressibleObjectTreeOptions { + readonly accessibilityProvider: IListAccessibilityProvider; +} + export class WorkbenchCompressibleObjectTree, TFilterData = void> extends CompressibleObjectTree { private internals: WorkbenchTreeInternals; @@ -802,7 +649,7 @@ export class WorkbenchCompressibleObjectTree, TFilter this.disposables.add(this.internals); } - updateOptions(options: IWorkbenchAsyncDataTreeOptions = {}): void { + updateOptions(options: IWorkbenchCompressibleObjectTreeOptionsUpdate = {}): void { super.updateOptions(options); if (options.overrideStyles) { @@ -811,10 +658,14 @@ export class WorkbenchCompressibleObjectTree, TFilter } } -export interface IWorkbenchDataTreeOptions extends IDataTreeOptions { +export interface IWorkbenchDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { readonly overrideStyles?: IColorMapping; } +export interface IWorkbenchDataTreeOptions extends IWorkbenchDataTreeOptionsUpdate, IDataTreeOptions { + readonly accessibilityProvider: IListAccessibilityProvider; +} + export class WorkbenchDataTree extends DataTree { private internals: WorkbenchTreeInternals; @@ -842,7 +693,7 @@ export class WorkbenchDataTree extends DataTree = {}): void { + updateOptions(options: IWorkbenchDataTreeOptionsUpdate = {}): void { super.updateOptions(options); if (options.overrideStyles) { @@ -851,10 +702,14 @@ export class WorkbenchDataTree extends DataTree extends IAsyncDataTreeOptions { +export interface IWorkbenchAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate { readonly overrideStyles?: IColorMapping; } +export interface IWorkbenchAsyncDataTreeOptions extends IWorkbenchAsyncDataTreeOptionsUpdate, IAsyncDataTreeOptions { + readonly accessibilityProvider: IListAccessibilityProvider; +} + export class WorkbenchAsyncDataTree extends AsyncDataTree { private internals: WorkbenchTreeInternals; @@ -882,7 +737,7 @@ export class WorkbenchAsyncDataTree extends Async this.disposables.add(this.internals); } - updateOptions(options: IWorkbenchAsyncDataTreeOptions = {}): void { + updateOptions(options: IWorkbenchAsyncDataTreeOptionsUpdate = {}): void { super.updateOptions(options); if (options.overrideStyles) { @@ -892,6 +747,7 @@ export class WorkbenchAsyncDataTree extends Async } export interface IWorkbenchCompressibleAsyncDataTreeOptions extends ICompressibleAsyncDataTreeOptions { + readonly accessibilityProvider: IListAccessibilityProvider; readonly overrideStyles?: IColorMapping; } @@ -1123,7 +979,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/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index ac71f17fc46..6a3b96f8214 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -8,6 +8,7 @@ import { createHash } from 'crypto'; import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { Queue } from 'vs/base/common/async'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; @@ -43,7 +44,7 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe constructor( @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService environmentService: INativeEnvironmentService, @ILogService private readonly logService: ILogService ) { super(); @@ -96,7 +97,7 @@ class LanguagePacksCache extends Disposable { private initializedCache: boolean | undefined; constructor( - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService environmentService: INativeEnvironmentService, @ILogService private readonly logService: ILogService ) { super(); diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 04cd3646418..59ffc8e9ccd 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -418,8 +418,8 @@ export function getLogLevel(environmentService: IEnvironmentService): LogLevel { if (environmentService.verbose) { return LogLevel.Trace; } - if (typeof environmentService.args.log === 'string') { - const logLevel = environmentService.args.log.toLowerCase(); + if (typeof environmentService.logLevel === 'string') { + const logLevel = environmentService.logLevel.toLowerCase(); switch (logLevel) { case 'trace': return LogLevel.Trace; diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 0d852a63de8..e89a52153d2 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -135,15 +135,15 @@ export namespace IMarkerData { export function makeKeyOptionalMessage(markerData: IMarkerData, useMessage: boolean): string { let result: string[] = [emptyString]; if (markerData.source) { - result.push(markerData.source.replace('¦', '\¦')); + result.push(markerData.source.replace('¦', '\\¦')); } else { result.push(emptyString); } if (markerData.code) { if (typeof markerData.code === 'string') { - result.push(markerData.code.replace('¦', '\¦')); + result.push(markerData.code.replace('¦', '\\¦')); } else { - result.push(markerData.code.value.replace('¦', '\¦')); + result.push(markerData.code.value.replace('¦', '\\¦')); } } else { result.push(emptyString); @@ -157,7 +157,7 @@ export namespace IMarkerData { // Modifed to not include the message as part of the marker key to work around // https://github.com/microsoft/vscode/issues/77475 if (markerData.message && useMessage) { - result.push(markerData.message.replace('¦', '\¦')); + result.push(markerData.message.replace('¦', '\\¦')); } else { result.push(emptyString); } diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 18c243a56ad..3f26d267855 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -7,7 +7,8 @@ import * as nls from 'vs/nls'; import { isMacintosh, language } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { app, shell, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, KeyboardEvent } from 'electron'; -import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { getTitleBarStyle, IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { OpenContext, IRunActionInWindowRequest, IRunKeybindingInWindowRequest } from 'vs/platform/windows/node/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; @@ -23,6 +24,7 @@ import { IStateService } from 'vs/platform/state/node/state'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; const telemetryFrom = 'menu'; @@ -65,7 +67,7 @@ export class Menubar { @IUpdateService private readonly updateService: IUpdateService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, @IStateService private readonly stateService: IStateService, @@ -532,6 +534,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 93429296cfe..4a7adc958ac 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -6,7 +6,7 @@ import BaseSeverity from 'vs/base/common/severity'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IAction } from 'vs/base/common/actions'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; export import Severity = BaseSeverity; @@ -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 @@ -101,6 +101,12 @@ export interface INotification extends INotificationProperties { * this usecase and much easier to use! */ actions?: INotificationActions; + + /** + * The initial set of progress properties for the notification. To update progress + * later on, access the `INotificationHandle.progress` property. + */ + readonly progress?: INotificationProgressProperties; } export interface INotificationActions { @@ -109,14 +115,32 @@ 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 { + + /** + * Causes the progress bar to spin infinitley. + */ + readonly infinite?: boolean; + + /** + * Indicate the total amount of work. + */ + readonly total?: number; + + /** + * Indicate that a specific chunk of work is done. + */ + readonly worked?: number; } export interface INotificationProgress { @@ -149,6 +173,13 @@ export interface INotificationHandle { */ readonly onDidClose: Event; + /** + * 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. + */ + readonly onDidChangeVisibility: Event; + /** * Allows to indicate progress on the notification even after the * notification is already visible. @@ -183,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. @@ -218,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 { @@ -318,16 +349,14 @@ export class NoOpNotification implements INotificationHandle { readonly progress = new NoOpProgress(); - private readonly _onDidClose: Emitter = new Emitter(); - readonly onDidClose: Event = this._onDidClose.event; + readonly onDidClose = Event.None; + readonly onDidChangeVisibility = Event.None; updateSeverity(severity: Severity): void { } updateMessage(message: NotificationMessage): void { } updateActions(actions?: INotificationActions): void { } - close(): void { - this._onDidClose.dispose(); - } + close(): void { } } export class NoOpProgress implements INotificationProgress { 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 804d1138561..87a3b99c703 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IProductConfiguration } from 'vs/platform/product/common/productService'; -import { assign } from 'vs/base/common/objects'; import { isWeb } from 'vs/base/common/platform'; import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -20,8 +19,8 @@ if (isWeb) { // Running out of sources if (Object.keys(product).length === 0) { - assign(product, { - version: '1.41.0-dev', + Object.assign(product, { + version: '1.45.0-dev', nameLong: 'Visual Studio Code Web Dev', nameShort: 'VSCode Web Dev', urlProtocol: 'code-oss' @@ -35,19 +34,19 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === ' // Obtain values from product.json and package.json const rootPath = path.dirname(getPathFromAmdModule(require, '')); - product = assign({}, require.__$__nodeRequire(path.join(rootPath, 'product.json')) as IProductConfiguration); + product = require.__$__nodeRequire(path.join(rootPath, 'product.json')); const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; }; // Running out of sources if (env['VSCODE_DEV']) { - assign(product, { + Object.assign(product, { nameShort: `${product.nameShort} Dev`, nameLong: `${product.nameLong} Dev`, dataFolderName: `${product.dataFolderName}-dev` }); } - assign(product, { + Object.assign(product, { version: pkg.version }); } diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 120fd666444..19e80ecca49 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -14,6 +14,13 @@ export interface IProductService extends Readonly { } +export interface IBuiltInExtension { + readonly name: string; + readonly version: string; + readonly repo: string; + readonly metadata: any; +} + export interface IProductConfiguration { readonly version: string; readonly date?: string; @@ -30,6 +37,8 @@ export interface IProductConfiguration { readonly urlProtocol: string; readonly dataFolderName: string; + readonly builtInExtensions?: IBuiltInExtension[]; + readonly downloadUrl?: string; readonly updateUrl?: string; readonly target?: string; @@ -49,6 +58,7 @@ export interface IProductConfiguration { readonly extensionTips?: { [id: string]: string; }; readonly extensionImportantTips?: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; }; + readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; }; readonly extensionKeywords?: { [extension: string]: readonly string[]; }; readonly keymapExtensionTips?: readonly string[]; @@ -88,7 +98,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; @@ -102,6 +112,8 @@ export interface IProductConfiguration { readonly msftInternalDomains?: string[]; readonly linkProtectionTrustedDomains?: readonly string[]; + + readonly 'configurationSync.store'?: { url: string, authenticationProviderId: string }; } export interface IExeBasedExtensionTip { @@ -112,6 +124,11 @@ export interface IExeBasedExtensionTip { exeFriendlyName?: string; } +export interface IRemoteExtensionTip { + friendlyName: string; + extensionId: string; +} + export interface ISurveyData { surveyId: string; surveyUrl: string; diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 2b7e3b8b385..2a5cfda1b32 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -17,7 +17,11 @@ export interface IProgressService { _serviceBrand: undefined; - withProgress(options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: (choice?: number) => void): Promise; + withProgress( + options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, + task: (progress: IProgress) => Promise, + onDidCancel?: (choice?: number) => void + ): Promise; } export interface IProgressIndicator { @@ -32,7 +36,7 @@ export interface IProgressIndicator { * Indicate progress for the duration of the provided promise. Progress will stop in * any case of promise completion, error or cancellation. */ - showWhile(promise: Promise, delay?: number): Promise; + showWhile(promise: Promise, delay?: number): Promise; } export const enum ProgressLocation { @@ -45,19 +49,20 @@ export const enum ProgressLocation { } export interface IProgressOptions { - location: ProgressLocation | string; - title?: string; - source?: string; - total?: number; - cancellable?: boolean; - buttons?: string[]; + readonly location: ProgressLocation | string; + readonly title?: string; + readonly source?: string; + readonly total?: number; + readonly cancellable?: boolean; + readonly buttons?: string[]; } export interface IProgressNotificationOptions extends IProgressOptions { readonly location: ProgressLocation.Notification; readonly primaryActions?: ReadonlyArray; readonly secondaryActions?: ReadonlyArray; - delay?: number; + readonly delay?: number; + readonly silent?: boolean; } export interface IProgressWindowOptions extends IProgressOptions { @@ -66,8 +71,8 @@ export interface IProgressWindowOptions extends IProgressOptions { } export interface IProgressCompositeOptions extends IProgressOptions { - location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; - delay?: number; + readonly location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; + readonly delay?: number; } export interface IProgressStep { @@ -82,8 +87,6 @@ export interface IProgressRunner { done(): void; } -export const emptyProgress: IProgress = { report: () => { } }; - export const emptyProgressRunner: IProgressRunner = Object.freeze({ total() { }, worked() { }, @@ -96,20 +99,16 @@ export interface IProgress { export class Progress implements IProgress { - private _callback: (data: T) => void; + static readonly None: IProgress = Object.freeze({ report() { } }); + private _value?: T; + get value(): T | undefined { return this._value; } - constructor(callback: (data: T) => void) { - this._callback = callback; - } - - get value(): T | undefined { - return this._value; - } + constructor(private callback: (data: T) => void) { } report(item: T) { this._value = item; - this._callback(this._value); + this.callback(this._value); } } 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..1ea7ca600a1 --- /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, IWorkbenchListOptions } from 'vs/platform/list/browser/listService'; +import { List } 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: IWorkbenchListOptions, + ) => 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<(O extends { canPickMany: true } ? T[] : T) | undefined> { + 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..6b6009967b5 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,31 +20,78 @@ 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. */ - pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; - pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): Promise; - pick(picks: Promise[]> | QuickPickInput[], options?: Omit, 'canPickMany'>, token?: CancellationToken): Promise; + 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; + pick(picks: Promise[]> | QuickPickInput[], options?: Omit, 'canPickMany'>, token?: CancellationToken): Promise; /** * Opens the quick input box for text input and returns a promise with the user typed value if any. */ - input(options?: IInputOptions, token?: CancellationToken): Promise; - - backButton: IQuickInputButton; + input(options?: IInputOptions, token?: CancellationToken): Promise; + /** + * 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 a34615001de..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[]; @@ -35,31 +35,29 @@ export enum RemoteAuthorityResolverErrorCode { Unknown = 'Unknown', NotAvailable = 'NotAvailable', TemporarilyNotAvailable = 'TemporarilyNotAvailable', + NoResolverFound = 'NoResolverFound' } 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 isTemporarilyNotAvailable(err: any): boolean { - if (err instanceof RemoteAuthorityResolverError) { - return err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable; - } - return false; + 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); @@ -67,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/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index 5578246cc82..feb46455ac4 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -12,6 +12,9 @@ export function getRemoteAuthority(uri: URI): string | undefined { return uri.scheme === REMOTE_HOST_SCHEME ? uri.authority : undefined; } +export function getRemoteName(authority: string): string; +export function getRemoteName(authority: undefined): undefined; +export function getRemoteName(authority: string | undefined): string | undefined; export function getRemoteName(authority: string | undefined): string | undefined { if (!authority) { return undefined; @@ -22,4 +25,4 @@ export function getRemoteName(authority: string | undefined): string | undefined return authority; } return authority.substr(0, pos); -} \ No newline at end of file +} 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..97abad3736d --- /dev/null +++ b/src/vs/platform/serviceMachineId/common/serviceMachineId.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { 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; + } + 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 (storageService) { + storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL); + } + return uuid; +} diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index badf5115935..4932ef70231 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -6,6 +6,7 @@ import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { writeFileSync, readFile } from 'vs/base/node/pfs'; import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; import { IStateService } from 'vs/platform/state/node/state'; @@ -132,7 +133,7 @@ export class StateService implements IStateService { private fileStorage: FileStorage; constructor( - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService environmentService: INativeEnvironmentService, @ILogService logService: ILogService ) { this.fileStorage = new FileStorage(path.join(environmentService.userDataPath, StateService.STATE_FILE), error => logService.error(error)); diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index bdadaa8c68b..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; @@ -224,7 +223,7 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase this.isWatching = true; this._register(this.fileService.watch(this.file)); - this._register(this.fileService.onFileChanges(e => { + this._register(this.fileService.onDidFilesChange(e => { if (document.hasFocus()) { return; // optimization: ignore changes from ourselves by checking for focus } @@ -240,9 +239,36 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase private async onDidStorageChangeExternal(): Promise { const items = await this.doGetItemsFromFile(); + // pervious cache, diff for changes + let changed = new Map(); + let deleted = new Set(); + if (this.cache) { + items.forEach((value, key) => { + const existingValue = this.cache?.get(key); + if (existingValue !== value) { + changed.set(key, value); + } + }); + + this.cache.forEach((_, key) => { + if (!items.has(key)) { + deleted.add(key); + } + }); + } + + // no previous cache, consider all as changed + else { + changed = items; + } + + // Update cache this.cache = items; - this._onDidChangeItemsExternal.fire({ items }); + // Emit as event as needed + if (changed.size > 0 || deleted.size > 0) { + this._onDidChangeItemsExternal.fire({ changed, deleted }); + } } async getItems(): Promise> { @@ -264,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 { @@ -284,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 e2bbca21641..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,33 +22,41 @@ interface ISerializableUpdateRequest { } interface ISerializableItemsChangeEvent { - items: Item[]; + readonly changed?: Item[]; + readonly deleted?: Key[]; } export class GlobalStorageDatabaseChannel extends Disposable implements IServerChannel { private static readonly STORAGE_CHANGE_DEBOUNCE_TIME = 100; - private readonly _onDidChangeItems: Emitter = this._register(new Emitter()); - readonly onDidChangeItems: Event = this._onDidChangeItems.event; + 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 @@ -99,15 +105,21 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC } private serializeEvents(events: IStorageChangeEvent[]): ISerializableItemsChangeEvent { - const items = new Map(); + const changed = new Map(); + const deleted = new Set(); events.forEach(event => { const existing = this.storageMainService.get(event.key); if (typeof existing === 'string') { - items.set(event.key, existing); + changed.set(event.key, existing); + } else { + deleted.add(event.key); } }); - return { items: mapToSerializable(items) }; + return { + changed: Array.from(changed.entries()), + deleted: Array.from(deleted.values()) + }; } listen(_: unknown, event: string): Event { @@ -126,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': { @@ -154,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; @@ -170,26 +182,29 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS } private onDidChangeItemsOnMain(e: ISerializableItemsChangeEvent): void { - if (Array.isArray(e.items)) { - this._onDidChangeItemsExternal.fire({ items: serializableToMap(e.items) }); + if (Array.isArray(e.changed) || Array.isArray(e.deleted)) { + this._onDidChangeItemsExternal.fire({ + changed: e.changed ? new Map(e.changed) : undefined, + deleted: e.deleted ? new Set(e.deleted) : undefined + }); } } 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/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index 3c34616f373..e90d77b5cb1 100644 --- a/src/vs/platform/storage/node/storageMainService.ts +++ b/src/vs/platform/storage/node/storageMainService.ts @@ -8,6 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage'; import { Storage, IStorage, InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage'; import { join } from 'vs/base/common/path'; @@ -103,7 +104,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic constructor( @ILogService private readonly logService: ILogService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: INativeEnvironmentService ) { super(); diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 1aed6216d8f..1c111c4d8cd 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/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 { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage'; import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage'; @@ -13,8 +13,8 @@ import { mark } from 'vs/base/common/performance'; 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 { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; 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'; @@ -25,13 +25,13 @@ export class NativeStorageService extends Disposable implements IStorageService private static readonly WORKSPACE_STORAGE_NAME = 'state.vscdb'; private static readonly WORKSPACE_META_NAME = 'workspace.json'; - 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 readonly _onWillSaveState: Emitter = this._register(new Emitter()); - readonly onWillSaveState: Event = this._onWillSaveState.event; + 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 +43,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 + @IEnvironmentService private readonly environmentService: INativeEnvironmentService ) { 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 +99,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 +161,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 +170,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 2b47b768fb7..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 @@ -426,6 +429,10 @@ export const minimapError = registerColor('minimap.errorHighlight', { dark: new export const minimapWarning = registerColor('minimap.warningHighlight', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Minimap marker color for warnings.')); export const minimapBackground = registerColor('minimap.background', { dark: null, light: null, hc: null }, nls.localize('minimapBackground', "Minimap background color.")); +export const minimapSliderBackground = registerColor('minimapSlider.background', { light: transparent(scrollbarSliderBackground, 0.5), dark: transparent(scrollbarSliderBackground, 0.5), hc: transparent(scrollbarSliderBackground, 0.5) }, nls.localize('minimapSliderBackground', "Minimap slider background color.")); +export const minimapSliderHoverBackground = registerColor('minimapSlider.hoverBackground', { light: transparent(scrollbarSliderHoverBackground, 0.5), dark: transparent(scrollbarSliderHoverBackground, 0.5), hc: transparent(scrollbarSliderHoverBackground, 0.5) }, nls.localize('minimapSliderHoverBackground', "Minimap slider background color when hovering.")); +export const minimapSliderActiveBackground = registerColor('minimapSlider.activeBackground', { light: transparent(scrollbarSliderActiveBackground, 0.5), dark: transparent(scrollbarSliderActiveBackground, 0.5), hc: transparent(scrollbarSliderActiveBackground, 0.5) }, nls.localize('minimapSliderActiveBackground', "Minimap slider background color when clicked on.")); + export const problemsErrorIconForeground = registerColor('problemsErrorIcon.foreground', { dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground }, nls.localize('problemsErrorIconForeground', "The color used for the problems error icon.")); export const problemsWarningIconForeground = registerColor('problemsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground }, nls.localize('problemsWarningIconForeground', "The color used for the problems warning icon.")); export const problemsInfoIconForeground = registerColor('problemsInfoIcon.foreground', { dark: editorInfoForeground, light: editorInfoForeground, hc: editorInfoForeground }, nls.localize('problemsInfoIconForeground', "The color used for the problems info icon.")); @@ -496,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 new file mode 100644 index 00000000000..935e3ffcb22 --- /dev/null +++ b/src/vs/platform/undoRedo/common/undoRedo.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; + +export const IUndoRedoService = createDecorator('undoRedoService'); + +export const enum UndoRedoElementType { + Resource, + Workspace +} + +export interface IResourceUndoRedoElement { + readonly type: UndoRedoElementType.Resource; + readonly resource: URI; + readonly label: string; + undo(): Promise | void; + redo(): Promise | void; +} + +export interface IWorkspaceUndoRedoElement { + readonly type: UndoRedoElementType.Workspace; + readonly resources: readonly URI[]; + readonly label: string; + undo(): Promise | void; + redo(): Promise | void; + split(): IResourceUndoRedoElement[]; +} + +export type IUndoRedoElement = IResourceUndoRedoElement | IWorkspaceUndoRedoElement; + +export interface IPastFutureElements { + past: IUndoRedoElement[]; + future: IUndoRedoElement[]; +} + +export interface IUndoRedoService { + _serviceBrand: undefined; + + /** + * Add a new element to the `undo` stack. + * This will destroy the `redo` stack. + */ + pushElement(element: IUndoRedoElement): void; + + /** + * Get the last pushed element. If the last pushed element has been undone, returns 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`. + */ + removeElements(resource: URI): void; + + canUndo(resource: URI): boolean; + undo(resource: URI): Promise | void; + + canRedo(resource: URI): boolean; + redo(resource: URI): Promise | void; +} diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts new file mode 100644 index 00000000000..9858c9eb6af --- /dev/null +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -0,0 +1,556 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo'; +import { URI } from 'vs/base/common/uri'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +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; + public readonly label: string; + + public readonly resource: URI; + public readonly strResource: string; + public readonly resources: URI[]; + public readonly strResources: string[]; + public isValid: boolean; + + constructor(actual: IResourceUndoRedoElement) { + this.actual = actual; + this.label = actual.label; + this.resource = actual.resource; + this.strResource = uriGetComparisonKey(this.resource); + this.resources = [this.resource]; + this.strResources = [this.strResource]; + this.isValid = true; + } + + public setValid(isValid: boolean): void { + this.isValid = isValid; + } +} + +const enum RemovedResourceReason { + ExternalRemoval = 0, + NoParallelUniverses = 1 +} + +class ResourceReasonPair { + constructor( + public readonly resource: URI, + public readonly reason: RemovedResourceReason + ) { } +} + +class RemovedResources { + private readonly elements = new Map(); + + private _getPath(resource: URI): string { + return resource.scheme === Schemas.file ? resource.fsPath : resource.path; + } + + public createMessage(): string { + 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)); + } + + 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 { + public readonly type = UndoRedoElementType.Workspace; + public readonly actual: IWorkspaceUndoRedoElement; + public readonly label: string; + + public readonly resources: URI[]; + public readonly strResources: string[]; + public removedResources: RemovedResources | null; + public invalidatedResources: RemovedResources | null; + + constructor(actual: IWorkspaceUndoRedoElement) { + this.actual = actual; + this.label = actual.label; + 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.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 { + public resource: URI; + public past: StackElement[]; + public future: StackElement[]; + + constructor(resource: URI) { + this.resource = resource; + this.past = []; + this.future = []; + } +} + +export class UndoRedoService implements IUndoRedoService { + _serviceBrand: undefined; + + private readonly _editStacks: Map; + + constructor( + @IDialogService private readonly _dialogService: IDialogService, + @INotificationService private readonly _notificationService: INotificationService, + ) { + this._editStacks = new Map(); + } + + 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]; + const strResource = element.strResources[i]; + + let editStack: ResourceEditStack; + if (this._editStacks.has(strResource)) { + editStack = this._editStacks.get(strResource)!; + } else { + editStack = new ResourceEditStack(resource); + this._editStacks.set(strResource, editStack); + } + + // remove the future + for (const futureElement of editStack.future) { + if (futureElement.type === UndoRedoElementType.Workspace) { + futureElement.removeResource(resource, strResource, RemovedResourceReason.NoParallelUniverses); + } + } + 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): IUndoRedoElement | null { + const strResource = uriGetComparisonKey(resource); + if (this._editStacks.has(strResource)) { + const editStack = this._editStacks.get(strResource)!; + if (editStack.future.length > 0) { + return null; + } + if (editStack.past.length === 0) { + return null; + } + return editStack.past[editStack.past.length - 1].actual; + } + return null; + } + + private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: RemovedResources | null): void { + const individualArr = toRemove.actual.split(); + const individualMap = new Map(); + for (const _element of individualArr) { + const element = new ResourceStackElement(_element); + individualMap.set(element.strResource, element); + } + + for (const strResource of toRemove.strResources) { + if (ignoreResources && ignoreResources.has(strResource)) { + continue; + } + const editStack = this._editStacks.get(strResource)!; + for (let j = editStack.past.length - 1; j >= 0; j--) { + if (editStack.past[j] === toRemove) { + if (individualMap.has(strResource)) { + // gets replaced + editStack.past[j] = individualMap.get(strResource)!; + } else { + // gets deleted + editStack.past.splice(j, 1); + } + break; + } + } + } + } + + private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: RemovedResources | null): void { + const individualArr = toRemove.actual.split(); + const individualMap = new Map(); + for (const _element of individualArr) { + const element = new ResourceStackElement(_element); + individualMap.set(element.strResource, element); + } + + for (const strResource of toRemove.strResources) { + if (ignoreResources && ignoreResources.has(strResource)) { + continue; + } + const editStack = this._editStacks.get(strResource)!; + for (let j = editStack.future.length - 1; j >= 0; j--) { + if (editStack.future[j] === toRemove) { + if (individualMap.has(strResource)) { + // gets replaced + editStack.future[j] = individualMap.get(strResource)!; + } else { + // gets deleted + editStack.future.splice(j, 1); + } + break; + } + } + } + } + + public removeElements(resource: URI): 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.removeResource(resource, strResource, RemovedResourceReason.ExternalRemoval); + } + } + for (const element of editStack.future) { + if (element.type === UndoRedoElementType.Workspace) { + element.removeResource(resource, strResource, RemovedResourceReason.ExternalRemoval); + } + } + this._editStacks.delete(strResource); + } + } + + 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)) { + const editStack = this._editStacks.get(strResource)!; + return (editStack.past.length > 0); + } + return false; + } + + private _onError(err: Error, element: StackElement): void { + onUnexpectedError(err); + // An error occured while undoing or redoing => drop the undo/redo stack for all affected resources + for (const resource of element.resources) { + this.removeElements(resource); + } + this._notificationService.error(err); + } + + private _safeInvoke(element: StackElement, invoke: () => Promise | void): Promise | void { + let result: Promise | void; + try { + result = invoke(); + } catch (err) { + return this._onError(err, element); + } + + if (result) { + return result.then(undefined, (err) => this._onError(err, element)); + } + } + + private _workspaceUndo(resource: URI, element: WorkspaceStackElement): Promise | void { + if (element.removedResources) { + 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[] = []; + for (const strResource of element.strResources) { + affectedEditStacks.push(this._editStacks.get(strResource)!); + } + + let cannotUndoDueToResources: URI[] = []; + for (const editStack of affectedEditStacks) { + if (editStack.past.length === 0 || editStack.past[editStack.past.length - 1] !== element) { + cannotUndoDueToResources.push(editStack.resource); + } + } + + if (cannotUndoDueToResources.length > 0) { + this._splitPastWorkspaceElement(element, null); + const paths = cannotUndoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path); + const message = nls.localize('cannotWorkspaceUndoDueToChanges', "Could not undo '{0}' across all files because changes were made to {1}", element.label, paths.join(', ')); + this._notificationService.info(message); + return this.undo(resource); + } + + return this._dialogService.show( + 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('cancel', "Cancel"), + ], + { + cancelId: 2 + } + ).then((result) => { + if (result.choice === 2) { + // cancel + return; + } else if (result.choice === 0) { + for (const editStack of affectedEditStacks) { + editStack.past.pop(); + editStack.future.push(element); + } + return this._safeInvoke(element, () => element.actual.undo()); + } else { + this._splitPastWorkspaceElement(element, null); + return this.undo(resource); + } + }); + } + + 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()); + } + + public undo(resource: URI): Promise | void { + const strResource = uriGetComparisonKey(resource); + if (!this._editStacks.has(strResource)) { + return; + } + + const editStack = this._editStacks.get(strResource)!; + if (editStack.past.length === 0) { + return; + } + + const element = editStack.past[editStack.past.length - 1]; + if (element.type === UndoRedoElementType.Workspace) { + return this._workspaceUndo(resource, element); + } else { + return this._resourceUndo(editStack, element); + } + } + + public canRedo(resource: URI): boolean { + const strResource = uriGetComparisonKey(resource); + if (this._editStacks.has(strResource)) { + const editStack = this._editStacks.get(strResource)!; + return (editStack.future.length > 0); + } + return false; + } + + private _workspaceRedo(resource: URI, element: WorkspaceStackElement): Promise | void { + if (element.removedResources) { + 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[] = []; + for (const strResource of element.strResources) { + affectedEditStacks.push(this._editStacks.get(strResource)!); + } + + let cannotRedoDueToResources: URI[] = []; + for (const editStack of affectedEditStacks) { + if (editStack.future.length === 0 || editStack.future[editStack.future.length - 1] !== element) { + cannotRedoDueToResources.push(editStack.resource); + } + } + + if (cannotRedoDueToResources.length > 0) { + this._splitFutureWorkspaceElement(element, null); + const paths = cannotRedoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path); + const message = nls.localize('cannotWorkspaceRedoDueToChanges', "Could not redo '{0}' across all files because changes were made to {1}", element.label, paths.join(', ')); + this._notificationService.info(message); + return this.redo(resource); + } + + for (const editStack of affectedEditStacks) { + editStack.future.pop(); + editStack.past.push(element); + } + return this._safeInvoke(element, () => element.actual.redo()); + } + + 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()); + } + + public redo(resource: URI): Promise | void { + const strResource = uriGetComparisonKey(resource); + if (!this._editStacks.has(strResource)) { + return; + } + + const editStack = this._editStacks.get(strResource)!; + if (editStack.future.length === 0) { + return; + } + + const element = editStack.future[editStack.future.length - 1]; + if (element.type === UndoRedoElementType.Workspace) { + return this._workspaceRedo(resource, element); + } else { + return this._resourceRedo(editStack, element); + } + } +} + +registerSingleton(IUndoRedoService, UndoRedoService); diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 380f553f5d8..65d1f28c961 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -10,6 +10,7 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec import product from 'vs/platform/product/common/product'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { IRequestService } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -46,7 +47,7 @@ export abstract class AbstractUpdateService implements IUpdateService { constructor( @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService protected configurationService: IConfigurationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @IRequestService protected requestService: IRequestService, @ILogService protected logService: ILogService, ) { } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index c3f9210c24e..f8fae801ca9 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -12,6 +12,7 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec import { State, IUpdate, StateType, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { AbstractUpdateService, createUpdateURL, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -31,7 +32,7 @@ export class DarwinUpdateService extends AbstractUpdateService { @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService environmentService: INativeEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService ) { diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index bdc5c591947..4b34dafeab8 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -9,6 +9,7 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec import { State, IUpdate, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { createUpdateURL, AbstractUpdateService, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; @@ -23,7 +24,7 @@ export class LinuxUpdateService extends AbstractUpdateService { @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService environmentService: INativeEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService ) { diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index 3516b44ad2c..a5720f4f045 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -8,6 +8,7 @@ import { timeout } from 'vs/base/common/async'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import * as path from 'vs/base/common/path'; import { realpath, watch } from 'fs'; @@ -36,7 +37,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { constructor( @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService environmentService: INativeEnvironmentService, @ILogService protected logService: ILogService, ) { if (environmentService.disableUpdates) { @@ -140,7 +141,7 @@ export class SnapUpdateService extends AbstractUpdateService2 { private snap: string, private snapRevision: string, @ILifecycleMainService lifecycleMainService: ILifecycleMainService, - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService environmentService: INativeEnvironmentService, @ILogService logService: ILogService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 7c7948d79b5..d8ff2bb11e5 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -13,6 +13,7 @@ import product from 'vs/platform/product/common/product'; import { State, IUpdate, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { createUpdateURL, AbstractUpdateService, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; @@ -63,7 +64,7 @@ export class Win32UpdateService extends AbstractUpdateService { @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IEnvironmentService environmentService: IEnvironmentService, + @IEnvironmentService environmentService: INativeEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService, @IFileService private readonly fileService: IFileService diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 6c979560016..ed42aa66ca9 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IURLService } from 'vs/platform/url/common/url'; import product from 'vs/platform/product/common/product'; import { app, Event as ElectronEvent } from 'electron'; @@ -12,7 +12,6 @@ import { URI } from 'vs/base/common/uri'; import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { isWindows } from 'vs/base/common/platform'; -import { coalesce } from 'vs/base/common/arrays'; import { disposableTimeout } from 'vs/base/common/async'; function uriFromRawUrl(url: string): URI | null { @@ -23,6 +22,16 @@ function uriFromRawUrl(url: string): URI | null { } } +/** + * A listener for URLs that are opened from the OS and handled by VSCode. + * Depending on the platform, this works differently: + * - Windows: we use `app.setAsDefaultProtocolClient()` to register VSCode with the OS + * and additionally add the `open-url` command line argument to identify. + * - macOS: we rely on `app.on('open-url')` to be called by the OS + * - Linux: we have a special shortcut installed (`resources/linux/code-url-handler.desktop`) + * that calls VSCode with the `open-url` command line argument + * (https://github.com/microsoft/vscode/pull/56727) + */ export class ElectronURLListener { private uris: URI[] = []; @@ -31,36 +40,34 @@ export class ElectronURLListener { private disposables = new DisposableStore(); constructor( - initial: string | string[], - @IURLService private readonly urlService: IURLService, - @IWindowsMainService windowsMainService: IWindowsMainService, - @IEnvironmentService environmentService: IEnvironmentService + initialUrisToHandle: URI[], + private readonly urlService: IURLService, + windowsMainService: IWindowsMainService, + environmentService: INativeEnvironmentService ) { - const globalBuffer = ((global).getOpenUrls() || []) as string[]; - const rawBuffer = [ - ...(typeof initial === 'string' ? [initial] : initial), - ...globalBuffer - ]; - this.uris = coalesce(rawBuffer.map(uriFromRawUrl)); + // the initial set of URIs we need to handle once the window is ready + this.uris = initialUrisToHandle; + // Windows: install as protocol handler if (isWindows) { const windowsParameters = environmentService.isBuilt ? [] : [`"${environmentService.appRoot}"`]; windowsParameters.push('--open-url', '--'); app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, windowsParameters); } + // macOS: listen to `open-url` events from here on to handle const onOpenElectronUrl = Event.map( Event.fromNodeEventEmitter(app, 'open-url', (event: ElectronEvent, url: string) => ({ event, url })), ({ event, url }) => { - // always prevent default and return the url as string - event.preventDefault(); + event.preventDefault(); // always prevent default and return the url as string return url; }); const onOpenUrl = Event.filter(Event.map(onOpenElectronUrl, uriFromRawUrl), (uri): uri is URI => !!uri); onOpenUrl(this.urlService.open, this.urlService, this.disposables); + // Send initial links to the window once it has loaded const isWindowReady = windowsMainService.getWindows() .filter(w => w.isReady) .length > 0; diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index ed01291c4cf..fd38c3bac80 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -4,51 +4,80 @@ *--------------------------------------------------------------------------------------------*/ 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'; 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 SyncConflictsClassification = { +type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; +export interface IRemoteUserData { + ref: string; + syncData: ISyncData | null; +} + +export interface ISyncData { + version: number; + content: string; +} + +function isSyncData(thing: any): thing is ISyncData { + return thing + && (thing.version && typeof thing.version === 'number') + && (thing.content && typeof thing.content === 'string') + && Object.keys(thing).length === 2; +} + 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.syncResourceLogLabel = uppercaseFirstLetter(this.resource); + this.syncFolder = joinPath(environmentService.userDataSyncHome, resource); + this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`); } protected setStatus(status: SyncStatus): void { @@ -58,37 +87,95 @@ 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 }, SyncConflictsClassification>('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 }, SyncConflictsClassification>('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); + } + } + + protected isEnabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resource); } async sync(ref?: string): Promise { - if (!this.enabled) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is disabled.`); + 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 = await this.getLastSyncUserData(); const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData); - return this.doSync(remoteUserData, lastSyncUserData); + + 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 { + 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.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; + } } async hasPreviouslySynced(): Promise { @@ -96,10 +183,34 @@ export abstract class AbstractSynchroniser extends Disposable { return !!lastSyncData; } - async getRemoteContent(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteUserData(lastSyncData); - return remoteUserData.content; + 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 { @@ -108,53 +219,90 @@ export abstract class AbstractSynchroniser extends Disposable { } catch (e) { /* ignore */ } } - protected async getLastSyncUserData(): Promise { + protected async getLastSyncUserData(): Promise { try { const content = await this.fileService.readFile(this.lastSyncResource); - return JSON.parse(content.value.toString()); + const parsed = JSON.parse(content.value.toString()); + let syncData: ISyncData = JSON.parse(parsed.content); + + // Migration from old content to sync data + if (!isSyncData(syncData)) { + syncData = { version: this.version, content: parsed.content }; + } + + return { ...parsed, ...{ syncData, content: undefined } }; } catch (error) { - return null; + if (!(error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND)) { + // log error always except when file does not exist + this.logService.error(error); + } } + return null; } - protected async updateLastSyncUserData(lastSyncUserData: T): Promise { + protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary = {}): Promise { + const lastSyncUserData: IUserData = { ref: lastSyncRemoteUserData.ref, content: JSON.stringify(lastSyncRemoteUserData.syncData), ...additionalProps }; await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); } - protected async getRemoteUserData(lastSyncData: IUserData | null): Promise { - return this.userDataSyncStoreService.read(this.resourceKey, lastSyncData, this.source); + protected async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise { + const { ref, content } = await this.getUserData(lastSyncData); + let syncData: ISyncData | null = null; + if (content !== null) { + syncData = this.parseSyncData(content); + } + return { ref, syncData }; } - protected async updateRemoteUserData(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(this.resourceKey, content, ref, this.source); + 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; } - protected async backupLocal(content: VSBuffer): Promise { - const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')); - await this.fileService.writeFile(resource, content); - this.cleanUpDelayer.trigger(() => this.cleanUpBackup()); - } - - private async cleanUpBackup(): Promise { - const stat = await this.fileService.resolve(this.syncFolder); - if (stat.children) { - const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)).sort(); - const toDelete = all.slice(0, Math.max(0, all.length - 9)); - await Promise.all(toDelete.map(stat => this.fileService.del(stat.resource))); + 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); } } - abstract readonly resourceKey: ResourceKey; - protected abstract doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise; + protected async updateRemoteUserData(content: string, ref: string | null): Promise { + const syncData: ISyncData = { version: this.version, content }; + ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref); + return { ref, syncData }; + } + + protected async backupLocal(content: string): Promise { + const syncData: ISyncData = { version: this.version, content }; + return this.userDataSyncBackupStoreService.backup(this.resource, JSON.stringify(syncData)); + } + + abstract stop(): Promise; + + protected abstract readonly version: number; + 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: IUserData; - readonly lastSyncUserData: IUserData | null; + readonly remoteUserData: IRemoteUserData; + readonly lastSyncUserData: IRemoteUserData | null; readonly content: string | null; - readonly hasLocalChanged: boolean; - readonly hasRemoteChanged: boolean; readonly hasConflicts: boolean; } @@ -164,36 +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); + super(resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(file))); - this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); + 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.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 { @@ -208,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 { @@ -229,7 +383,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { return; } - if (!this.enabled) { + if (!this.isEnabled()) { return; } @@ -237,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)); }); } @@ -255,23 +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); + super(file, resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); } protected hasErrors(content: string): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index 5d02314b264..bb02ff61d84 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -29,6 +29,12 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync }; } + // massage incoming extension - add disabled property + const massageIncomingExtension = (extension: ISyncExtension): ISyncExtension => ({ ...extension, ...{ disabled: !!extension.disabled } }); + localExtensions = localExtensions.map(massageIncomingExtension); + remoteExtensions = remoteExtensions.map(massageIncomingExtension); + lastSyncExtensions = lastSyncExtensions ? lastSyncExtensions.map(massageIncomingExtension) : null; + const uuids: Map = new Map(); const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } }; localExtensions.forEach(({ identifier }) => addUUID(identifier)); @@ -37,10 +43,12 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier)); } - const addExtensionToMap = (map: Map, extension: ISyncExtension) => { + const getKey = (extension: ISyncExtension): string => { const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase()); - const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`; - map.set(key, extension); + return uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`; + }; + const addExtensionToMap = (map: Map, extension: ISyncExtension) => { + map.set(getKey(extension), extension); return map; }; const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map()); @@ -62,14 +70,17 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet); const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet); - const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => { + // massage outgoing extension - remove disabled property + const massageOutgoingExtension = (extension: ISyncExtension, key: string): ISyncExtension => { const massagedExtension: ISyncExtension = { identifier: { id: extension.identifier.id, uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined }, - enabled: extension.enabled, }; + if (extension.disabled) { + massagedExtension.disabled = true; + } if (extension.version) { massagedExtension.version = extension.version; } @@ -90,25 +101,25 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync if (baseToLocal.added.has(key)) { // Is different from local to remote if (localToRemote.updated.has(key)) { - updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); + updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key)); } } else { // Add to local - added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); + added.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key)); } } // Remotely updated extensions for (const key of values(baseToRemote.updated)) { // Update in local always - updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); + updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key)); } // Locally added extensions for (const key of values(baseToLocal.added)) { // Not there in remote if (!baseToRemote.added.has(key)) { - newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key)); + newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!); } } @@ -121,7 +132,7 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync // If not updated in remote if (!baseToRemote.updated.has(key)) { - newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key)); + newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!); } } @@ -133,9 +144,13 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync } } + const remote: ISyncExtension[] = []; const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set()); - const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null; - return { added, removed, updated, remote }; + if (remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0) { + newRemoteExtensionsMap.forEach((value, key) => remote.push(massageOutgoingExtension(value, key))); + } + + return { added, removed, updated, remote: remote.length ? remote : null }; } function compare(from: Map | null, to: Map, ignoredExtensions: Set): { added: Set, removed: Set, updated: Set } { @@ -152,7 +167,7 @@ function compare(from: Map | null, to: Map( @@ -58,75 +64,98 @@ 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(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - if (remoteUserData.content !== null) { + if (remoteUserData.syncData !== null) { const localExtensions = await this.getLocalExtensions(); - const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.content); - const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions()); - await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData }); + 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, + 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, 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 { @@ -141,76 +170,71 @@ 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: IUserData, lastSyncUserData: ILastSyncUserData | null): Promise { - try { - const previewResult = await this.getPreview(remoteUserData, lastSyncUserData); - await this.apply(previewResult); - } catch (e) { - this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.RemotePreconditionFailed) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Extensions: Failed to synchronize extensions as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - throw e; - } - - this.logService.trace('Extensions: Finished synchronizing extensions.'); - this.setStatus(SyncStatus.Idle); - } - - private async getPreview(remoteUserData: IUserData, lastSyncUserData: ILastSyncUserData | null): Promise { - const remoteExtensions: ISyncExtension[] = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; - const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.content!) : null; + 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 || [] : []; 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, 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 }: 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(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); - const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - remoteUserData = { ref, content }; - this.logService.info('Extensions: Updated remote extensions'); + remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote extensions`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { // update last sync - this.logService.trace('Extensions: Updating last synchronized extensions...'); - await this.updateLastSyncUserData({ ...remoteUserData, skippedExtensions }); - this.logService.info('Extensions: Updated last synchronized extensions'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized extensions...`); + await this.updateLastSyncUserData(remoteUserData, { skippedExtensions }); + 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); })); } @@ -236,14 +260,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // Builtin Extension: Sync only enablement state if (installedExtension && installedExtension.type === ExtensionType.System) { - if (e.enabled) { - this.logService.trace('Extensions: Enabling extension...', e.identifier.id); - await this.extensionEnablementService.enableExtension(e.identifier); - this.logService.info('Extensions: Enabled extension', e.identifier.id); - } else { - this.logService.trace('Extensions: Disabling extension...', e.identifier.id); + if (e.disabled) { + 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(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id); + await this.extensionEnablementService.enableExtension(e.identifier); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id); } removeFromSkipped.push(e.identifier); return; @@ -252,26 +276,26 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version); if (extension) { try { - if (e.enabled) { - this.logService.trace('Extensions: Enabling extension...', e.identifier.id, extension.version); - await this.extensionEnablementService.enableExtension(extension.identifier); - this.logService.info('Extensions: Enabled extension', e.identifier.id, extension.version); - } else { - this.logService.trace('Extensions: Disabling extension...', e.identifier.id, extension.version); + if (e.disabled) { + 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(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version); + await this.extensionEnablementService.enableExtension(extension.identifier); + 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); @@ -293,11 +317,33 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return newSkippedExtensions; } + private parseExtensions(syncData: ISyncData): ISyncExtension[] { + let extensions: ISyncExtension[] = JSON.parse(syncData.content); + if (syncData.version !== this.version) { + extensions = extensions.map(e => { + // #region Migration from v1 (enabled -> disabled) + if (!(e).enabled) { + e.disabled = true; + } + delete (e).enabled; + // #endregion + return e; + }); + } + return extensions; + } + private async getLocalExtensions(): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); - const disabledExtensions = await this.extensionEnablementService.getDisabledExtensions(); + const disabledExtensions = this.extensionEnablementService.getDisabledExtensions(); return installedExtensions - .map(({ identifier }) => ({ identifier, enabled: !disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)) })); + .map(({ identifier }) => { + const syncExntesion: ISyncExtension = { identifier }; + if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { + syncExntesion.disabled = true; + } + return syncExntesion; + }); } } 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 489ff558ab4..aad86c57f60 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,94 +3,133 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncError, UserDataSyncErrorCode, 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'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; import { parse } from 'vs/base/common/json'; -import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +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; - readonly remoteUserData: IUserData; - readonly lastSyncUserData: IUserData | null; +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: 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); + 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.onFileChanges, 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.content !== null) { - const local: IGlobalState = JSON.parse(remoteUserData.content); - await this.apply({ local, remote: undefined, remoteUserData, lastSyncUserData }); + if (remoteUserData.syncData !== null) { + 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 remote = await this.getLocalGlobalState(); - const lastSyncUserData = await this.getLastSyncUserData(); + const localUserData = await this.getLocalGlobalState(); + const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - await this.apply({ local: undefined, remote, remoteUserData, 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); } @@ -99,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) { @@ -115,88 +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: IUserData, lastSyncUserData: IUserData | 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); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.RemotePreconditionFailed) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('UI State: Failed to synchronize ui state as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - throw e; - } finally { - this.setStatus(SyncStatus.Idle); - } - } - - private async getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, ): Promise { - const remoteGlobalState: IGlobalState = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; - const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null; + protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { + const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.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, 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 }: 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...'); + 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); - const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - this.logService.info('UI State: Updated remote ui state'); - remoteUserData = { ref, content }; + 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(`${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 a381e1f96b8..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, IUserData, 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'; @@ -16,9 +16,10 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { isUndefined } from 'vs/base/common/types'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +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,37 +30,39 @@ 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 private readonly configurationService: IConfigurationService, + @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); + 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(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - const content = remoteUserData.content !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; + const content = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; if (content !== null) { const fileContent = await this.getLocalFileContent(); @@ -77,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); } @@ -88,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(); @@ -118,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 })); @@ -155,38 +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: IUserData, lastSyncUserData: IUserData | 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.RemotePreconditionFailed: - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new remote version available. Synchronizing again...'); - return this.sync(); 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; @@ -202,51 +221,53 @@ 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.content); - const ref = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref); - remoteUserData = { ref, content: remoteContents }; - this.logService.info('Keybindings: Updated remote keybindings'); + 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(`${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); - await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent }); - this.logService.info('Keybindings: Updated last synchronized keybindings'); + 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(`${this.syncResourceLogLabel}: Updated last synchronized keybindings`); } this.syncPreviewResultPromise = null; } - private getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise { + private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { if (!this.syncPreviewResultPromise) { this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token)); } return this.syncPreviewResultPromise; } - private async generatePreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, token: CancellationToken): Promise { - const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; - const lastSyncContent = lastSyncUserData && lastSyncUserData.content ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.content) : null; + 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 const fileContent = await this.getLocalFileContent(); const formattingOptions = await this.getFormattingOptions(); @@ -259,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) { @@ -280,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 }; } @@ -312,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/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index 9b9b146fd39..cd74d415f26 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -10,8 +10,8 @@ import { values } from 'vs/base/common/map'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions, Edit, getEOL } from 'vs/base/common/jsonFormatter'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; -import { IConflictSetting, CONFIGURATION_SYNC_STORE_KEY } from 'vs/platform/userDataSync/common/userDataSync'; -import { firstIndex } from 'vs/base/common/arrays'; +import { IConflictSetting, getDisallowedIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; +import { firstIndex, distinct } from 'vs/base/common/arrays'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { startsWith } from 'vs/base/common/strings'; @@ -22,7 +22,7 @@ export interface IMergeResult { conflictsSettings: IConflictSetting[]; } -export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] { +export function getIgnoredSettings(defaultIgnoredSettings: string[], configurationService: IConfigurationService, settingsContent?: string): string[] { let value: string[] = []; if (settingsContent) { const setting = parse(settingsContent); @@ -32,7 +32,7 @@ export function getIgnoredSettings(configurationService: IConfigurationService, } else { value = configurationService.getValue('sync.ignoredSettings'); } - const added: string[] = [], removed: string[] = []; + const added: string[] = [], removed: string[] = [...getDisallowedIgnoredSettings()]; if (Array.isArray(value)) { for (const key of value) { if (startsWith(key, '-')) { @@ -42,7 +42,7 @@ export function getIgnoredSettings(configurationService: IConfigurationService, } } } - return [CONFIGURATION_SYNC_STORE_KEY, ...added].filter(setting => removed.indexOf(setting) === -1); + return distinct([...defaultIgnoredSettings, ...added,].filter(setting => removed.indexOf(setting) === -1)); } @@ -576,15 +576,17 @@ function parseSettings(content: string): INode[] { if (hierarchyLevel === 0) { if (sep === ',') { const node = nodes.pop(); - nodes.push({ - startOffset: node!.startOffset, - endOffset: node!.endOffset, - value: node!.value, - setting: { - key: node!.setting!.key, - hasCommaSeparator: true - } - }); + if (node) { + nodes.push({ + startOffset: node.startOffset, + endOffset: node.endOffset, + value: node.value, + setting: { + key: node.setting!.key, + hasCommaSeparator: true + } + }); + } } } }, diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 8848fe0ddac..e159e8188c3 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,47 +4,55 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, IUserData, 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 } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +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'; -export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService { +export interface ISettingsSyncContent { + settings: string; +} + +function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent { + return thing + && (thing.settings && typeof thing.settings === 'string') + && Object.keys(thing).length === 1; +} + +export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { _serviceBrand: any; - readonly resourceKey: ResourceKey = 'settings'; - 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 version: number = 1; + 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 private readonly configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, ) { - super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService); + super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } protected setStatus(status: SyncStatus): void { @@ -54,35 +62,28 @@ 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(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); + const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); - if (remoteUserData.content !== null) { + if (remoteSettingsSyncContent !== null) { const fileContent = await this.getLocalFileContent(); const formatUtils = await this.getFormattingOptions(); // Update ignored settings from local file content - const content = updateIgnoredSettings(remoteUserData.content, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(); + const content = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils); this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, remoteUserData, @@ -98,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(); @@ -124,7 +125,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (fileContent !== null) { const formatUtils = await this.getFormattingOptions(); // Remove ignored settings - const content = updateIgnoredSettings(fileContent.value.toString(), '{}', getIgnoredSettings(this.configurationService), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(); + const content = updateIgnoredSettings(fileContent.value.toString(), '{}', ignoredSettings, formatUtils); const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); @@ -143,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); } @@ -171,23 +173,59 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return false; } - async getRemoteContent(preview?: boolean): Promise { - let content = await super.getRemoteContent(preview); - if (preview && content !== null) { + 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 (content !== null) { const formatUtils = await this.getFormattingOptions(); // remove ignored settings from the remote content for preview - content = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(); + content = updateIgnoredSettings(content, '{}', ignoredSettings, formatUtils); } 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(); // Add ignored settings from local file content - content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(); + content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils); this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content })); await this.apply(true); this.setStatus(SyncStatus.Idle); @@ -198,37 +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: IUserData, lastSyncUserData: IUserData | 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.RemotePreconditionFailed: - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Settings: Failed to synchronize settings as there is a new remote version available. Synchronizing again...'); - return this.sync(); 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; @@ -244,93 +271,129 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (content !== null) { - 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); - } + 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(); // Update ignored settings from remote - content = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils); - this.logService.trace('Settings: Updating remote settings...'); - const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - this.logService.info('Settings: Updated remote settings'); - remoteUserData = { ref, content }; + const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); + const ignoredSettings = await this.getIgnoredSettings(content); + content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); + 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: IUserData, lastSyncUserData: IUserData | 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: IUserData, lastSyncUserData: IUserData | 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); + const lastSettingsSyncContent = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null; let content: string | null = null; let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; - let conflictSettings: IConflictSetting[] = []; - if (remoteUserData.content) { + if (remoteSettingsSyncContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; - - // No action when there are errors - if (this.hasErrors(localContent)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); - } - - else { - this.logService.trace('Settings: Merging remote settings with local settings...'); - const result = merge(localContent, remoteUserData.content, lastSyncUserData ? lastSyncUserData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions); - content = result.localContent || result.remoteContent; - hasLocalChanged = result.localContent !== null; - hasRemoteChanged = result.remoteContent !== null; - hasConflicts = result.hasConflicts; - conflictSettings = result.conflictsSettings; - } + this.validateContent(localContent); + 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; } // 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; } if (content && !token.isCancellationRequested) { // Remove the ignored settings from the preview. - const previewContent = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formattingOptions); - await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent)); + const ignoredSettings = await this.getIgnoredSettings(); + const previewContent = updateIgnoredSettings(content, '{}', ignoredSettings, formattingOptions); + 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 }; } + private getSettingsSyncContent(remoteUserData: IRemoteUserData): ISettingsSyncContent | null { + return remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null; + } + + private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null { + try { + const parsed = JSON.parse(syncContent); + return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent }; + } catch (e) { + this.logService.error(e); + } + return null; + } + + private toSettingsSyncContent(settings: string): ISettingsSyncContent { + return { settings }; + } + + private _defaultIgnoredSettings: Promise | undefined = undefined; + protected async getIgnoredSettings(content?: string): Promise { + if (!this._defaultIgnoredSettings) { + this._defaultIgnoredSettings = this.userDataSyncUtilService.resolveDefaultIgnoredSettings(); + const disposable = Event.any( + Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)), + Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)))(() => { + disposable.dispose(); + this._defaultIgnoredSettings = undefined; + }); + } + const defaultIgnoredSettings = await this._defaultIgnoredSettings; + return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content); + } + + 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.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..3f710d0b44a --- /dev/null +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -0,0 +1,428 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + const extension = extname(resource); + if (extension === '.json' || extension === '.code-snippet') { + 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/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index e4d1c0ae4f8..b623b9754a0 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -6,7 +6,13 @@ import { timeout, Delayer } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncSource, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +type AutoSyncTriggerClassification = { + source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService { @@ -16,22 +22,23 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto private successiveFailures: number = 0; private readonly syncDelayer: Delayer; - private readonly _onError: Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._register(new Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }>()); - readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event; + private readonly _onError: Emitter = this._register(new Emitter()); + readonly onError: Event = this._onError.event; constructor( @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, + @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); this.updateEnablement(false, true); this.syncDelayer = this._register(new Delayer(0)); - this._register(Event.any(userDataAuthTokenService.onDidChangeToken)(() => this.updateEnablement(true, true))); + this._register(Event.any(authTokenService.onDidChangeToken)(() => this.updateEnablement(true, true))); this._register(Event.any(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true, true))); this._register(this.userDataSyncEnablementService.onDidChangeEnablement(() => this.updateEnablement(true, false))); - this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync())); + this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync(['resourceEnablement']))); } private async updateEnablement(stopIfDisabled: boolean, auto: boolean): Promise { @@ -61,20 +68,23 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto await this.userDataSyncService.sync(); this.resetFailures(); } catch (e) { - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.TurnedOff) { + const error = UserDataSyncError.toUserDataSyncError(e); + if (error.code === UserDataSyncErrorCode.TurnedOff || error.code === UserDataSyncErrorCode.SessionExpired) { this.logService.info('Auto Sync: Sync is turned off in the cloud.'); this.logService.info('Auto Sync: Resetting the local sync state.'); await this.userDataSyncService.resetLocal(); this.logService.info('Auto Sync: Completed resetting the local sync state.'); if (auto) { - return this.userDataSyncEnablementService.setEnablement(false); + this.userDataSyncEnablementService.setEnablement(false); + this._onError.fire(error); + return; } else { return this.sync(loop, auto); } } - this.logService.error(e); + this.logService.error(error); this.successiveFailures++; - this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); + this._onError.fire(error); } if (loop) { await timeout(1000 * 60 * 5); @@ -88,14 +98,15 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto private async isAutoSyncEnabled(): Promise { return this.userDataSyncEnablementService.isEnabled() && this.userDataSyncService.status !== SyncStatus.Uninitialized - && !!(await this.userDataAuthTokenService.getToken()); + && !!(await this.authTokenService.getToken()); } private resetFailures(): void { this.successiveFailures = 0; } - async triggerAutoSync(): Promise { + async triggerAutoSync(sources: string[]): Promise { + sources.forEach(source => this.telemetryService.publicLog2<{ source: string }, AutoSyncTriggerClassification>('sync/triggerAutoSync', { source })); if (this.enabled) { return this.syncDelayer.trigger(() => { this.logService.info('Auto Sync: Triggered.'); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 585a64b6377..f88be9c8d93 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionIdentifier, EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, allSettings } from 'vs/platform/configuration/common/configurationRegistry'; @@ -18,8 +18,10 @@ 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 } 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'; export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; @@ -36,8 +38,21 @@ export interface ISyncConfiguration { } } +export function getDisallowedIgnoredSettings(): string[] { + const allSettings = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + return Object.keys(allSettings).filter(setting => !!allSettings[setting].disallowSyncIgnore); +} + +export function getDefaultIgnoredSettings(): string[] { + const allSettings = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + const machineSettings = Object.keys(allSettings).filter(setting => allSettings[setting].scope === ConfigurationScope.MACHINE || allSettings[setting].scope === ConfigurationScope.MACHINE_OVERRIDABLE); + const disallowedSettings = getDisallowedIgnoredSettings(); + return distinct([CONFIGURATION_SYNC_STORE_KEY, ...machineSettings, ...disallowedSettings]); +} + export function registerConfiguration(): IDisposable { const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings'; + const ignoredExtensionsSchemaId = 'vscode://schemas/ignoredExtensions'; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ id: 'sync', @@ -45,48 +60,22 @@ export function registerConfiguration(): IDisposable { title: localize('sync', "Sync"), type: 'object', properties: { - 'sync.enable': { - type: 'boolean', - default: false, - scope: ConfigurationScope.APPLICATION, - deprecationMessage: 'deprecated' - }, - 'sync.enableSettings': { - type: 'boolean', - default: true, - scope: ConfigurationScope.APPLICATION, - deprecationMessage: 'deprecated' - }, - 'sync.enableKeybindings': { - type: 'boolean', - default: true, - scope: ConfigurationScope.APPLICATION, - deprecationMessage: 'Deprecated' - }, - 'sync.enableUIState': { - type: 'boolean', - default: true, - scope: ConfigurationScope.APPLICATION, - deprecationMessage: 'deprecated' - }, - 'sync.enableExtensions': { - type: 'boolean', - default: true, - scope: ConfigurationScope.APPLICATION, - deprecationMessage: 'deprecated' - }, 'sync.keybindingsPerPlatform': { type: 'boolean', description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."), default: true, scope: ConfigurationScope.APPLICATION, + tags: ['sync', 'usesOnlineServices'] }, 'sync.ignoredExtensions': { 'type': 'array', - description: localize('sync.ignoredExtensions', "Configure extensions to be ignored while synchronizing."), + 'description': localize('sync.ignoredExtensions', "List of extensions to be ignored while synchronizing. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp."), + $ref: ignoredExtensionsSchemaId, 'default': [], 'scope': ConfigurationScope.APPLICATION, - uniqueItems: true + uniqueItems: true, + disallowSyncIgnore: true, + tags: ['sync', 'usesOnlineServices'] }, 'sync.ignoredSettings': { 'type': 'array', @@ -95,20 +84,31 @@ export function registerConfiguration(): IDisposable { 'scope': ConfigurationScope.APPLICATION, $ref: ignoredSettingsSchemaId, additionalProperties: true, - uniqueItems: true + uniqueItems: true, + disallowSyncIgnore: true, + tags: ['sync', 'usesOnlineServices'] } } }); + const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); const registerIgnoredSettingsSchema = () => { - const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); + const disallowedIgnoredSettings = getDisallowedIgnoredSettings(); + const defaultIgnoredSettings = getDefaultIgnoredSettings().filter(s => s !== CONFIGURATION_SYNC_STORE_KEY); + const settings = Object.keys(allSettings.properties).filter(setting => defaultIgnoredSettings.indexOf(setting) === -1); + const ignoredSettings = defaultIgnoredSettings.filter(setting => disallowedIgnoredSettings.indexOf(setting) === -1); const ignoredSettingsSchema: IJSONSchema = { items: { type: 'string', - enum: Object.keys(allSettings.properties) - } + enum: [...settings, ...ignoredSettings.map(setting => `-${setting}`)] + }, }; jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema); }; + jsonRegistry.registerSchema(ignoredExtensionsSchemaId, { + type: 'string', + pattern: EXTENSION_IDENTIFIER_PATTERN, + errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.") + }); return configurationRegistry.onDidUpdateConfiguration(() => registerIgnoredSettingsSchema()); } @@ -120,33 +120,59 @@ export interface IUserData { } export interface IUserDataSyncStore { - url: string; + url: URI; authenticationProviderId: string; } -export function getUserDataSyncStore(configurationService: IConfigurationService): IUserDataSyncStore | undefined { - const value = configurationService.getValue(CONFIGURATION_SYNC_STORE_KEY); - return value && value.url && value.authenticationProviderId ? value : undefined; +export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined { + 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'), + authenticationProviderId: value.authenticationProviderId + }; + } + 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 { - settings: string; - keybindings: string; - extensions: string; - globalState: string; + 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 @@ -162,19 +188,22 @@ export enum UserDataSyncErrorCode { TooLarge = 'TooLarge', NoRef = 'NoRef', TurnedOff = 'TurnedOff', + SessionExpired = 'SessionExpired', // Local Errors LocalPreconditionFailed = 'LocalPreconditionFailed', LocalInvalidContent = 'LocalInvalidContent', + LocalError = 'LocalError', + Incompatible = 'Incompatible', Unknown = 'Unknown', } 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 { @@ -183,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); } @@ -199,19 +228,16 @@ export class UserDataSyncStoreError extends UserDataSyncError { } export interface ISyncExtension { identifier: IExtensionIdentifier; version?: string; - enabled: boolean; + disabled?: boolean; +} + +export interface IStorageValue { + version: number; + value: string; } export interface IGlobalState { - argv: IStringDictionary; - storage: IStringDictionary; -} - -export const enum SyncSource { - Settings = 'Settings', - Keybindings = 'Keybindings', - Extensions = 'Extensions', - GlobalState = 'GlobalState' + storage: IStringDictionary; } export const enum SyncStatus { @@ -221,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; @@ -234,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 @@ -251,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; @@ -267,10 +313,14 @@ 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 onDidChangeLocal: Event; + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]>; + + readonly lastSyncTime: number | undefined; + readonly onDidChangeLastSyncTime: Event; pull(): Promise; sync(): Promise; @@ -279,15 +329,19 @@ 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'); export interface IUserDataAutoSyncService { _serviceBrand: any; - readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>; - triggerAutoSync(): Promise; + readonly onError: Event; + triggerAutoSync(sources: string[]): Promise; } export const IUserDataSyncUtilService = createDecorator('IUserDataSyncUtilService'); @@ -295,17 +349,7 @@ export interface IUserDataSyncUtilService { _serviceBrand: undefined; resolveUserBindings(userbindings: string[]): Promise>; resolveFormattingOptions(resource: URI): Promise; -} - -export const IUserDataAuthTokenService = createDecorator('IUserDataAuthTokenService'); - -export interface IUserDataAuthTokenService { - _serviceBrand: undefined; - - readonly onDidChangeToken: Event; - - getToken(): Promise; - setToken(accessToken: string | undefined): Promise; + resolveDefaultIgnoredSettings(): Promise; } export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); @@ -317,31 +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 CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); - 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 CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); +export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); + +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 ea7cbca4cd6..991af91c775 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -3,13 +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 { @@ -18,13 +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 + @IStorageService private readonly storageService: IStorageService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IEnvironmentService environmentService: IEnvironmentService, ) { super(); + switch (environmentService.sync) { + case 'on': + this.setEnablement(true); + break; + case 'off': + this.setEnablement(false); + break; + } this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); } @@ -34,17 +50,20 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa setEnablement(enabled: boolean): void { if (this.isEnabled() !== enabled) { + this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(enablementKey, { enabled }); this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL); } } - 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) { - this.storageService.store(getEnablementKey(resourceKey), enabled, StorageScope.GLOBAL); + 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); } } @@ -54,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 ec38e651598..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, IUserDataAuthTokenService, 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 { @@ -19,53 +21,26 @@ export class UserDataSyncChannel implements IServerChannel { case 'onDidChangeStatus': return this.service.onDidChangeStatus; case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; case 'onDidChangeLocal': return this.service.onDidChangeLocal; + case 'onDidChangeLastSyncTime': return this.service.onDidChangeLastSyncTime; + case 'onSyncErrors': return this.service.onSyncErrors; } throw new Error(`Event not found: ${event}`); } call(context: any, command: string, args?: any): Promise { switch (command) { - case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflictsSources]); - 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'); } @@ -84,26 +59,7 @@ export class UserDataAutoSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { - case 'triggerAutoSync': return this.service.triggerAutoSync(); - } - throw new Error('Invalid call'); - } -} - -export class UserDataAuthTokenServiceChannel implements IServerChannel { - constructor(private readonly service: IUserDataAuthTokenService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChangeToken': return this.service.onDidChangeToken; - } - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case 'setToken': return this.service.setToken(args); - case 'getToken': return this.service.getToken(); + case 'triggerAutoSync': return this.service.triggerAutoSync(args[0]); } throw new Error('Invalid call'); } @@ -119,6 +75,7 @@ export class UserDataSycnUtilServiceChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { + case 'resolveDefaultIgnoredSettings': return this.service.resolveDefaultIgnoredSettings(); case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]); case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0])); } @@ -133,6 +90,10 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { constructor(private readonly channel: IChannel) { } + async resolveDefaultIgnoredSettings(): Promise { + return this.channel.call('resolveDefaultIgnoredSettings'); + } + async resolveUserBindings(userbindings: string[]): Promise> { return this.channel.call('resolveUserKeybindings', [userbindings]); } @@ -143,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 adede4eb82d..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, IUserDataAuthTokenService, 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'; @@ -14,11 +14,19 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; 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 }; }; +const SESSION_ID_KEY = 'sync.sessionId'; +const LAST_SYNC_TIME_KEY = 'sync.lastSyncTime'; + export class UserDataSyncService extends Disposable implements IUserDataSyncService { _serviceBrand: any; @@ -30,38 +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: [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, - @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @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(this.userDataAuthTokenService.onDidChangeToken(e => this.onDidChangeAuthTokenStatus(e))); + this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeConflicts, () => undefined)))(() => this.updateConflicts())); } - this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); + 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.resource))); } async pull(): Promise { @@ -70,9 +91,10 @@ 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(); } async push(): Promise { @@ -81,22 +103,24 @@ 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(); } async sync(): Promise { await this.checkEnablement(); const startTime = new Date().getTime(); + this._syncErrors = []; try { this.logService.trace('Sync started.'); if (this.status !== SyncStatus.HasConflicts) { this.setStatus(SyncStatus.Syncing); } - const manifest = await this.userDataSyncStoreService.manifest(); + let manifest = await this.userDataSyncStoreService.manifest(); // Server has no data but this machine was synced before if (manifest === null && await this.hasPreviouslySynced()) { @@ -104,22 +128,42 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff); } + const sessionId = this.storageService.get(SESSION_ID_KEY, StorageScope.GLOBAL); + // Server session is different from client session + if (sessionId && manifest && sessionId !== manifest.session) { + throw new UserDataSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired); + } + for (const synchroniser of this.synchronisers) { try { - await synchroniser.sync(manifest ? manifest[synchroniser.resourceKey] : undefined); + await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resource] : undefined); } catch (e) { - this.handleSyncError(e, synchroniser.source); + this.handleSyncError(e, synchroniser.resource); + this._syncErrors.push([synchroniser.resource, UserDataSyncError.toUserDataSyncError(e)]); } } + // After syncing, get the manifest if it was not available before + if (manifest === null) { + manifest = await this.userDataSyncStoreService.manifest(); + } + + // Update local session id + if (manifest && manifest.session !== sessionId) { + this.storageService.store(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL); + } + this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`); + this.updateLastSyncTime(); } finally { this.updateStatus(); + this._onSyncErrors.fire(this._syncErrors); } } async stop(): Promise { + await this.checkEnablement(); if (this.status === SyncStatus.Idle) { return; } @@ -134,42 +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); - return synchroniser.accept(content); - } - - private async hasPreviouslySynced(): Promise { - await this.checkEnablement(); - for (const synchroniser of this.synchronisers) { - if (await synchroniser.hasPreviouslySynced()) { - return true; - } + 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); } - return false; } - private async hasLocalData(): Promise { - await this.checkEnablement(); + async resolveContent(resource: URI): Promise { for (const synchroniser of this.synchronisers) { - if (await synchroniser.hasLocalData()) { - return true; - } - } - return false; - } - - async getRemoteContent(source: SyncSource, preview: boolean): Promise { - await this.checkEnablement(); - 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()) { @@ -178,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 { @@ -189,16 +237,36 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ async resetLocal(): Promise { await this.checkEnablement(); + this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL); + this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL); for (const synchroniser of this.synchronisers) { try { synchroniser.resetLocal(); } catch (e) { - this.logService.error(`${synchroniser.source}: ${toErrorMessage(e)}`); + this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`); this.logService.error(e); } } } + private async hasPreviouslySynced(): Promise { + for (const synchroniser of this.synchronisers) { + if (await synchroniser.hasPreviouslySynced()) { + return true; + } + } + return false; + } + + private async hasLocalData(): Promise { + for (const synchroniser of this.synchronisers) { + if (await synchroniser.hasLocalData()) { + return true; + } + } + return false; + } + private async resetRemote(): Promise { await this.checkEnablement(); try { @@ -209,22 +277,30 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private setStatus(status: SyncStatus): void { + const oldStatus = this._status; if (this._status !== status) { this._status = status; this._onDidChangeStatus.fire(status); + if (oldStatus === SyncStatus.HasConflicts) { + this.updateLastSyncTime(); + } } } 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; @@ -238,7 +314,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return SyncStatus.Idle; } - private handleSyncError(e: Error, source: SyncSource): void { + private updateLastSyncTime(): void { + if (this.status === SyncStatus.Idle) { + this._lastSyncTime = new Date().getTime(); + this.storageService.store(LAST_SYNC_TIME_KEY, this._lastSyncTime, StorageScope.GLOBAL); + this._onDidChangeLastSyncTime.fire(this._lastSyncTime); + } + } + + private handleSyncError(e: Error, source: SyncResource): void { if (e instanceof UserDataSyncStoreError) { switch (e.code) { case UserDataSyncErrorCode.TooLarge: @@ -250,31 +334,19 @@ 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 { if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new UserDataSyncError('Not Authenticated. Please sign in to start sync.', UserDataSyncErrorCode.Unauthorized); - } } - private onDidChangeAuthTokenStatus(token: string | undefined): void { - if (!token) { - this.stop(); - } - } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 5d17b1de189..7856c7fd71e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,36 +4,108 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, 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 { URI } from 'vs/base/common/uri'; -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, @IConfigurationService configurationService: IConfigurationService, @IRequestService private readonly requestService: IRequestService, - @IUserDataAuthTokenService private readonly authTokenService: IUserDataAuthTokenService, + @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, ) { super(); - this.userDataSyncStore = getUserDataSyncStore(configurationService); + this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); + this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService) + .then(uuid => { + const headers: IHeaders = { + 'X-Sync-Client-Id': productService.version, + }; + 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(URI.parse(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'; @@ -41,7 +113,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. @@ -49,37 +121,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(URI.parse(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; } @@ -89,7 +161,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn throw new Error('No settings sync store url configured.'); } - const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', 'latest').toString(); + const url = joinPath(this.userDataSyncStore.url, 'manifest').toString(); const headers: IHeaders = { 'Content-Type': 'application/json' }; const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None); @@ -105,7 +177,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn throw new Error('No settings sync store url configured.'); } - const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource').toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource').toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); @@ -115,13 +187,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 } } }); @@ -134,6 +209,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } if (context.res.statusCode === 401) { + this.authTokenService.sendTokenFailed(); throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' failed because of Unauthorized (401).`, UserDataSyncErrorCode.Unauthorized, source); } diff --git a/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts index 097c1b23eee..6fa694e1d27 100644 --- a/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncLogService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; +import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { @@ -15,16 +17,16 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { @IUserDataSyncService userDataSyncService: IUserDataSyncService, @IElectronService electronService: IElectronService, @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService, + @IAuthenticationTokenService authTokenService: IAuthenticationTokenService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService); + super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService, telemetryService); - // Sync immediately if there is a local change. - this._register(Event.debounce(Event.any( - electronService.onWindowFocus, - electronService.onWindowOpen, + this._register(Event.debounce(Event.any( + Event.map(electronService.onWindowFocus, () => 'windowFocus'), + Event.map(electronService.onWindowOpen, () => 'windowOpen'), userDataSyncService.onDidChangeLocal, - ), () => undefined, 500)(() => this.triggerAutoSync())); + ), (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerAutoSync(sources))); } } diff --git a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index b98154aeb5e..3bd7057806f 100644 --- a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -11,9 +11,9 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge returns local extension if remote does not exist', async () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, null, null, [], []); @@ -26,13 +26,13 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge returns local extension if remote does not exist with ignored extensions', async () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, null, null, [], ['a']); @@ -45,13 +45,13 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', async () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, null, null, [], ['A']); @@ -64,17 +64,17 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge returns local extension if remote does not exist with skipped extensions', async () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const skippedExtension: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, null, null, skippedExtension, []); @@ -87,16 +87,16 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge returns local extension if remote does not exist with skipped and ignored extensions', async () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const skippedExtension: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, null, null, skippedExtension, ['a']); @@ -109,23 +109,23 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when there is no base', async () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); @@ -133,22 +133,22 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when there is no base and with ignored extensions', async () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], ['a']); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); @@ -156,43 +156,66 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when remote is moved forwarded', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]); assert.deepEqual(actual.updated, []); assert.equal(actual.remote, null); }); - test('merge local and remote extensions when remote moved forwarded with ignored extensions', async () => { + test('merge local and remote extensions when remote is moved forwarded with disabled extension', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, + { identifier: { id: 'd', uuid: 'd' }, disabled: true }, + ]; + + const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); + + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); + assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]); + assert.deepEqual(actual.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true }]); + assert.equal(actual.remote, null); + }); + + test('merge local and remote extensions when remote moved forwarded with ignored extensions', async () => { + const baseExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, + ]; + const localExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, + ]; + const remoteExtensions: ISyncExtension[] = [ + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a']); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); assert.deepEqual(actual.updated, []); assert.equal(actual.remote, null); @@ -200,23 +223,23 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when remote is moved forwarded with skipped extensions', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]); assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); assert.deepEqual(actual.updated, []); assert.equal(actual.remote, null); @@ -224,23 +247,23 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when remote is moved forwarded with skipped and ignored extensions', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']); - assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' } }]); assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]); assert.deepEqual(actual.updated, []); assert.equal(actual.remote, null); @@ -248,16 +271,39 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when local is moved forwarded', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, + ]; + + const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); + + assert.deepEqual(actual.added, []); + assert.deepEqual(actual.removed, []); + assert.deepEqual(actual.updated, []); + assert.deepEqual(actual.remote, localExtensions); + }); + + test('merge local and remote extensions when local is moved forwarded with disabled extensions', async () => { + const baseExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, + ]; + const localExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' }, disabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, + ]; + const remoteExtensions: ISyncExtension[] = [ + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); @@ -270,16 +316,16 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when local is moved forwarded with ignored settings', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']); @@ -288,30 +334,30 @@ suite('ExtensionsMerge - No Conflicts', () => { assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, [ - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'c', uuid: 'c' } }, ]); }); test('merge local and remote extensions when local is moved forwarded with skipped extensions', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); @@ -324,23 +370,23 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when local is moved forwarded with skipped and ignored extensions', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']); @@ -353,28 +399,28 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when both moved forwarded', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'e', uuid: 'e' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'e', uuid: 'e' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'e', uuid: 'e' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'e', uuid: 'e' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' } }]); assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); @@ -382,23 +428,23 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when both moved forwarded with ignored extensions', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'e', uuid: 'e' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'e', uuid: 'e' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'e', uuid: 'e' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'e', uuid: 'e' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']); @@ -411,30 +457,30 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when both moved forwarded with skipped extensions', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'e', uuid: 'e' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'e', uuid: 'e' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'e', uuid: 'e' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'e', uuid: 'e' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); - assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' } }]); assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); @@ -442,25 +488,25 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge local and remote extensions when both moved forwarded with skipped and ignoredextensions', async () => { const baseExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const skippedExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, ]; const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'e', uuid: 'e' }, enabled: true }, + { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'e', uuid: 'e' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'e', uuid: 'e' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'e', uuid: 'e' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']); @@ -473,24 +519,24 @@ suite('ExtensionsMerge - No Conflicts', () => { test('merge when remote extension has no uuid and different extension id case', async () => { const localExtensions: ISyncExtension[] = [ - { identifier: { id: 'a', uuid: 'a' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'a', uuid: 'a' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const remoteExtensions: ISyncExtension[] = [ - { identifier: { id: 'A' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, + { identifier: { id: 'A' } }, + { identifier: { id: 'd', uuid: 'd' } }, ]; const expected: ISyncExtension[] = [ - { identifier: { id: 'A' }, enabled: true }, - { identifier: { id: 'd', uuid: 'd' }, enabled: true }, - { identifier: { id: 'b', uuid: 'b' }, enabled: true }, - { identifier: { id: 'c', uuid: 'c' }, enabled: true }, + { identifier: { id: 'A', uuid: 'a' } }, + { identifier: { id: 'd', uuid: 'd' } }, + { identifier: { id: 'b', uuid: 'b' } }, + { identifier: { id: 'c', uuid: 'c' } }, ]; const actual = merge(localExtensions, remoteExtensions, null, [], []); - assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, enabled: true }]); + assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' } }]); assert.deepEqual(actual.removed, []); assert.deepEqual(actual.updated, []); assert.deepEqual(actual.remote, expected); 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/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index 3780af3ab57..24475726d8f 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -5,11 +5,7 @@ import * as assert from 'assert'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; -import { IStringDictionary } from 'vs/base/common/collections'; -import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; -import { FormattingOptions } from 'vs/base/common/jsonFormatter'; -import { URI } from 'vs/base/common/uri'; -import type { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { TestUserDataSyncUtilService } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; suite('KeybindingsMerge - No Conflicts', () => { @@ -613,7 +609,7 @@ suite('KeybindingsMerge - No Conflicts', () => { }); async function mergeKeybindings(localContent: string, remoteContent: string, baseContent: string | null) { - const userDataSyncUtilService = new MockUserDataSyncUtilService(); + const userDataSyncUtilService = new TestUserDataSyncUtilService(); const formattingOptions = await userDataSyncUtilService.resolveFormattingOptions(); return merge(localContent, remoteContent, baseContent, formattingOptions, userDataSyncUtilService); } @@ -621,22 +617,3 @@ async function mergeKeybindings(localContent: string, remoteContent: string, bas function stringify(value: any): string { return JSON.stringify(value, null, '\t'); } - -class MockUserDataSyncUtilService implements IUserDataSyncUtilService { - - _serviceBrand: any; - - async resolveUserBindings(userbindings: string[]): Promise> { - const keys: IStringDictionary = {}; - for (const keybinding of userbindings) { - keys[keybinding] = keybinding; - } - return keys; - } - - async resolveFormattingOptions(file?: URI): Promise { - return { eol: '\n', insertSpaces: false, tabSize: 4 }; - } - - async ignoreExtensionsToSync(extensions: IExtensionIdentifier[]): Promise { } -} 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..5546792c101 --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -0,0 +1,676 @@ +/*--------------------------------------------------------------------------------------------- + * 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" + } +}`; + +const globalSnippet = `{ + // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. 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, and {1: label}, { 2: another } for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } +}`; + +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); + }); + + test('sync global and language snippet', async () => { + await updateSnippet('global.code-snippet', globalSnippet, client2); + await updateSnippet('html.json', htmlSnippet1, 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('global.code-snippet', testClient); + assert.equal(actual2, globalSnippet); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'global.code-snippet': globalSnippet }); + }); + + test('sync should ignore non snippets', async () => { + await updateSnippet('global.code-snippet', globalSnippet, client2); + await updateSnippet('html.html', 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('typescript.json', testClient); + assert.equal(actual1, tsSnippet1); + const actual2 = await readSnippet('global.code-snippet', testClient); + assert.equal(actual2, globalSnippet); + const actual3 = await readSnippet('html.html', testClient); + assert.equal(actual3, null); + + const { content } = await testClient.read(testObject.resource); + assert.ok(content !== null); + const actual = parseSnippets(content!); + assert.deepEqual(actual, { 'typescript.json': tsSnippet1, 'global.code-snippet': globalSnippet }); + }); + + 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 new file mode 100644 index 00000000000..881a90a8796 --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.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 { 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, 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'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { NullLogService, ILogService } from 'vs/platform/log/common/log'; +import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IFileService } from 'vs/platform/files/common/files'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { Schemas } from 'vs/base/common/network'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; +import { IGlobalExtensionEnablementService, IExtensionManagementService, IExtensionGalleryService, DidInstallExtensionEvent, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { Disposable } from 'vs/base/common/lifecycle'; +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 { + + readonly instantiationService: TestInstantiationService; + + constructor(readonly testServer: UserDataSyncTestServer = new UserDataSyncTestServer()) { + super(); + this.instantiationService = new TestInstantiationService(); + } + + async setUp(empty: boolean = false): Promise { + const userDataDirectory = URI.file('userdata').with({ scheme: Schemas.inMemory }); + const userDataSyncHome = joinPath(userDataDirectory, '.sync'); + const environmentService = this.instantiationService.stub(IEnvironmentService, >{ + userDataSyncHome, + settingsResource: joinPath(userDataDirectory, 'settings.json'), + keybindingsResource: joinPath(userDataDirectory, 'keybindings.json'), + snippetsHome: joinPath(userDataDirectory, 'snippets'), + argvResource: joinPath(userDataDirectory, 'argv.json') + }); + + const logService = new NullLogService(); + this.instantiationService.stub(ILogService, logService); + + 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()); + this.instantiationService.stub(IFileService, fileService); + + this.instantiationService.stub(IStorageService, new InMemoryStorageService()); + + const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); + await configurationService.initialize(); + this.instantiationService.stub(IConfigurationService, configurationService); + + this.instantiationService.stub(IRequestService, this.testServer); + this.instantiationService.stub(IAuthenticationTokenService, >{ + onDidChangeToken: new Emitter().event, + async getToken() { return 'token'; } + }); + + 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, >{ + async getInstalled() { return []; }, + onDidInstallExtension: new Emitter().event, + onDidUninstallExtension: new Emitter().event, + }); + this.instantiationService.stub(IExtensionGalleryService, >{ + isEnabled() { return true; }, + async getCompatibleExtension() { return null; } + }); + + this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService)); + + 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 { + + _serviceBrand: any; + + readonly url: string = 'http://host:3000'; + private session: string | null = null; + 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; } + + private _responses: { status: number }[] = []; + get responses(): { status: number }[] { return this._responses; } + reset(): void { this._requests = []; this._responses = []; } + + async resolveProxy(url: string): Promise { return url; } + + async request(options: IRequestOptions, token: CancellationToken): Promise { + const headers: IHeaders = {}; + if (options.headers) { + if (options.headers['If-None-Match']) { + headers['If-None-Match'] = options.headers['If-None-Match']; + } + if (options.headers['If-Match']) { + headers['If-Match'] = options.headers['If-Match']; + } + } + this._requests.push({ url: options.url!, type: options.type!, headers }); + const requestContext = await this.doRequest(options); + this._responses.push({ status: requestContext.res.statusCode! }); + return requestContext; + } + + private async doRequest(options: IRequestOptions): Promise { + const versionUrl = `${this.url}/v1/`; + const relativePath = options.url!.indexOf(versionUrl) === 0 ? options.url!.substring(versionUrl.length) : undefined; + const segments = relativePath ? relativePath.split('/') : []; + if (options.type === 'GET' && segments.length === 1 && segments[0] === 'manifest') { + return this.getManifest(options.headers); + } + if (options.type === 'GET' && segments.length === 3 && segments[0] === 'resource' && segments[2] === 'latest') { + return this.getLatestData(segments[1], options.headers); + } + if (options.type === 'POST' && segments.length === 2 && segments[0] === 'resource') { + return this.writeData(segments[1], options.data, options.headers); + } + if (options.type === 'DELETE' && segments.length === 1 && segments[0] === 'resource') { + return this.clear(options.headers); + } + return this.toResponse(501); + } + + private async getManifest(headers?: IHeaders): Promise { + if (this.session) { + 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)); + } + return this.toResponse(204); + } + + private async getLatestData(resource: string, headers: IHeaders = {}): Promise { + const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource); + if (resourceKey) { + const data = this.data.get(resourceKey); + if (!data) { + return this.toResponse(204, { etag: '0' }); + } + if (headers['If-None-Match'] === data.ref) { + return this.toResponse(304); + } + return this.toResponse(200, { etag: data.ref }, data.content || ''); + } + return this.toResponse(204); + } + + private async writeData(resource: string, content: string = '', headers: IHeaders = {}): Promise { + if (!this.session) { + this.session = generateUuid(); + } + const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource); + if (resourceKey) { + const data = this.data.get(resourceKey); + if (headers['If-Match'] !== undefined && headers['If-Match'] !== (data ? data.ref : '0')) { + return this.toResponse(412); + } + const ref = `${parseInt(data?.ref || '0') + 1}`; + this.data.set(resourceKey, { ref, content }); + return this.toResponse(200, { etag: ref }); + } + return this.toResponse(204); + } + + private async clear(headers?: IHeaders): Promise { + this.data.clear(); + this.session = null; + return this.toResponse(204); + } + + private toResponse(statusCode: number, headers?: IHeaders, data?: string): IRequestContext { + return { + res: { + headers: headers || {}, + statusCode + }, + stream: bufferToStream(VSBuffer.fromString(data || '')) + }; + } +} + +export class TestUserDataSyncUtilService implements IUserDataSyncUtilService { + + _serviceBrand: any; + + async resolveDefaultIgnoredSettings(): Promise { + return getDefaultIgnoredSettings(); + } + + async resolveUserBindings(userbindings: string[]): Promise> { + const keys: IStringDictionary = {}; + for (const keybinding of userbindings) { + keys[keybinding] = keybinding; + } + return keys; + } + + async resolveFormattingOptions(file?: URI): Promise { + return { eol: '\n', insertSpaces: false, tabSize: 4 }; + } + +} + diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts new file mode 100644 index 00000000000..5c2b51c2840 --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -0,0 +1,575 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { 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', () => { + + const disposableStore = new DisposableStore(); + + teardown(() => disposableStore.clear()); + + test('test first time sync ever', async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncService); + + // Sync for first time + await testObject.sync(); + + assert.deepEqual(target.requests, [ + // Manifest + { 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': '0' } }, + // 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' } }, + // Extensions + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } }, + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + ]); + + }); + + test('test first time sync ever with no data', async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(true); + const testObject = client.instantiationService.get(IUserDataSyncService); + + // Sync for first time + await testObject.sync(); + + assert.deepEqual(target.requests, [ + // Manifest + { 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: {} }, + // 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' } }, + // Extensions + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } }, + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + ]); + + }); + + test('test first time sync from the client with no changes - pull', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Setup the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + + // Sync (pull) from the test client + target.reset(); + await testObject.isFirstTimeSyncWithMerge(); + await testObject.pull(); + + assert.deepEqual(target.requests, [ + /* first time 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/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: {} }, + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + ]); + + }); + + test('test first time sync from the client with changes - pull', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Setup the test client with changes + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + const fileService = testClient.instantiationService.get(IFileService); + const environmentService = testClient.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(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); + + // Sync (pull) from the test client + target.reset(); + await testObject.isFirstTimeSyncWithMerge(); + await testObject.pull(); + + assert.deepEqual(target.requests, [ + /* first time sync */ + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/settings/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: {} }, + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + ]); + + }); + + test('test first time sync from the client with no changes - merge', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Setup the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + + // Sync (merge) from the test client + target.reset(); + await testObject.isFirstTimeSyncWithMerge(); + await testObject.sync(); + + assert.deepEqual(target.requests, [ + /* first time 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/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: {} }, + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + ]); + + }); + + test('test first time sync from the client with changes - merge', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Setup the test client with changes + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const fileService = testClient.instantiationService.get(IFileService); + const environmentService = testClient.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(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 + target.reset(); + await testObject.isFirstTimeSyncWithMerge(); + await testObject.sync(); + + assert.deepEqual(target.requests, [ + /* 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: {} }, + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } }, + { 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: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + ]); + + }); + + test('test sync when there are no changes', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + + // sync from the client again + target.reset(); + await testObject.sync(); + + assert.deepEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + ]); + }); + + test('test sync when there are local changes', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + target.reset(); + + // Do changes in the client + const fileService = client.instantiationService.get(IFileService); + 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 + await testObject.sync(); + + assert.deepEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Settings + { 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' } }, + ]); + }); + + test('test sync when there are remote changes', async () => { + const target = new UserDataSyncTestServer(); + + // Sync from first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Sync from test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + + // Do changes in first client and sync + const fileService = client.instantiationService.get(IFileService); + 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(); + + // Sync from test client + target.reset(); + await testObject.sync(); + + assert.deepEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Settings + { 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' } }, + ]); + + }); + + test('test delete', async () => { + const target = new UserDataSyncTestServer(); + + // Sync from the client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + + // Reset from the client + target.reset(); + await testObject.reset(); + + assert.deepEqual(target.requests, [ + // Manifest + { type: 'DELETE', url: `${target.url}/v1/resource`, headers: {} }, + ]); + + }); + + test('test delete and sync', async () => { + const target = new UserDataSyncTestServer(); + + // Sync from the client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + + // Reset from the client + await testObject.reset(); + + // Sync again + target.reset(); + await testObject.sync(); + + assert.deepEqual(target.requests, [ + // Manifest + { 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': '0' } }, + // 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' } }, + // Extensions + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/extensions`, headers: { 'If-Match': '0' } }, + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + ]); + + }); + + test('test delete on one client throws turned off error on other client while syncing', async () => { + const target = new UserDataSyncTestServer(); + + // Set up and sync from the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + + // Reset from the first client + await client.instantiationService.get(IUserDataSyncService).reset(); + + // Sync from the test client + target.reset(); + try { + await testObject.sync(); + } catch (e) { + assert.ok(e instanceof UserDataSyncError); + assert.deepEqual((e).code, UserDataSyncErrorCode.TurnedOff); + assert.deepEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + ]); + return; + } + throw assert.fail('Should fail with turned off error'); + }); + + test('test creating new session from one client throws session expired error on another client while syncing', async () => { + const target = new UserDataSyncTestServer(); + + // Set up and sync from the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + + // Reset from the first client + await client.instantiationService.get(IUserDataSyncService).reset(); + + // Sync again from the first client to create new session + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Sync from the test client + target.reset(); + try { + await testObject.sync(); + } catch (e) { + assert.ok(e instanceof UserDataSyncError); + assert.deepEqual((e).code, UserDataSyncErrorCode.SessionExpired); + assert.deepEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + ]); + return; + } + throw assert.fail('Should fail with turned off error'); + }); + + test('test sync status', async () => { + const target = new UserDataSyncTestServer(); + + // Setup the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject = client.instantiationService.get(IUserDataSyncService); + + // sync from the client + const actualStatuses: SyncStatus[] = []; + const disposable = testObject.onDidChangeStatus(status => actualStatuses.push(status)); + await testObject.sync(); + + disposable.dispose(); + 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 () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + let fileService = client.instantiationService.get(IFileService); + let environmentService = client.instantiationService.get(IEnvironmentService); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Setup the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + fileService = testClient.instantiationService.get(IFileService); + environmentService = testClient.instantiationService.get(IEnvironmentService); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 16 }))); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + + // sync from the client + await testObject.sync(); + + assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + assert.deepEqual(testObject.conflicts.map(({ syncResource }) => syncResource), [SyncResource.Settings]); + }); + + test('test sync will sync other non conflicted areas', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + let fileService = client.instantiationService.get(IFileService); + let environmentService = client.instantiationService.get(IEnvironmentService); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Setup the test client and get conflicts in settings + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + let testFileService = testClient.instantiationService.get(IFileService); + let testEnvironmentService = testClient.instantiationService.get(IEnvironmentService); + await testFileService.writeFile(testEnvironmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 16 }))); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + + // sync from the first client with changes in keybindings + await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // sync from the test client + target.reset(); + const actualStatuses: SyncStatus[] = []; + const disposable = testObject.onDidChangeStatus(status => actualStatuses.push(status)); + await testObject.sync(); + + disposable.dispose(); + assert.deepEqual(actualStatuses, []); + assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + + assert.deepEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Keybindings + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: { 'If-None-Match': '1' } }, + ]); + }); + + test('test stop sync reset status', async () => { + const target = new UserDataSyncTestServer(); + + // Setup and sync from the first client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + let fileService = client.instantiationService.get(IFileService); + let environmentService = client.instantiationService.get(IEnvironmentService); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); + await client.instantiationService.get(IUserDataSyncService).sync(); + + // Setup the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + fileService = testClient.instantiationService.get(IFileService); + environmentService = testClient.instantiationService.get(IEnvironmentService); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 16 }))); + const testObject = testClient.instantiationService.get(IUserDataSyncService); + await testObject.sync(); + + // sync from the client + await testObject.stop(); + + assert.deepEqual(testObject.status, SyncStatus.Idle); + assert.deepEqual(testObject.conflicts, []); + }); + +}); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 78210255a5f..fb1eecf6085 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -3,22 +3,11 @@ * 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 { 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 { isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -export interface IOpenedWindow { - id: number; - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - title: string; - filename?: string; -} - export interface IBaseOpenWindowsOptions { forceReuseWindow?: boolean; } @@ -131,66 +120,12 @@ export function getTitleBarStyle(configurationService: IConfigurationService, en return isLinux ? 'native' : 'custom'; // default to custom on all macOS and Windows } -export const enum OpenContext { - - // opening when running from the command line - CLI, - - // macOS only: opening from the dock (also when opening files to a running instance from desktop) - DOCK, - - // opening from the main application window - MENU, - - // opening from a file or folder dialog - DIALOG, - - // opening from the OS's UI - DESKTOP, - - // opening through the API - API -} - -export const enum ReadyState { - - /** - * This window has not loaded any HTML yet - */ - NONE, - - /** - * This window is loading HTML - */ - LOADING, - - /** - * This window is navigating to another HTML - */ - NAVIGATING, - - /** - * This window is done loading HTML - */ - READY -} - export interface IPath extends IPathData { // the file path to open within the instance fileUri?: URI; } -export interface IPathsToWaitFor extends IPathsToWaitForData { - paths: IPath[]; - waitMarkerFileUri: URI; -} - -export interface IPathsToWaitForData { - paths: IPathData[]; - waitMarkerFileUri: UriComponents; -} - export interface IPathData { // the file path to open within the instance @@ -211,58 +146,15 @@ export interface IPathData { export interface IOpenFileRequest { filesToOpenOrCreate?: IPathData[]; filesToDiff?: IPathData[]; - filesToWait?: IPathsToWaitForData; - termProgram?: string; } -export interface IAddFoldersRequest { - foldersToAdd: UriComponents[]; -} - -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? +export interface IWindowConfiguration { 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 { - id: string; - from: 'menu' | 'touchbar' | 'mouse'; - args?: any[]; -} - -export interface IRunKeybindingInWindowRequest { - userSettingsLabel: string; } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 9d519473683..0db6846abd9 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,8 +3,9 @@ * 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 { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { INativeWindowConfiguration, OpenContext } from 'vs/platform/windows/node/window'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment } from 'vs/base/common/platform'; @@ -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 a5554fed40f..5191a0366d7 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -7,10 +7,12 @@ import * as fs from 'fs'; import { basename, normalize, join, posix } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; -import { assign, mixin } from 'vs/base/common/objects'; +import { mixin } from 'vs/base/common/objects'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; import { ipcMain as ipc, screen, BrowserWindow, MessageBoxOptions, Display, app, nativeTheme } from 'electron'; @@ -18,8 +20,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, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri, INativeWindowConfiguration, OpenContext, IAddFoldersRequest, IPathsToWaitFor } 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'; @@ -176,7 +178,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, @IStateService private readonly stateService: IStateService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -454,16 +456,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(); - foldersToAdd.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f), isRestored: true }))); - // 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 +495,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,14 +1353,14 @@ 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; configuration.mainPid = process.pid; configuration.execPath = process.execPath; - configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {}); + configuration.userEnv = { ...this.initialUserEnv, ...options.userEnv }; configuration.isInitialStartup = options.initialStartup; configuration.workspace = options.workspace; configuration.folderUri = options.folderUri; @@ -1482,7 +1481,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 +1499,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..051be14afd5 100644 --- a/src/vs/platform/windows/node/window.ts +++ b/src/vs/platform/windows/node/window.ts @@ -3,12 +3,102 @@ * 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 { URI } from 'vs/base/common/uri'; +import { IOpenWindowOptions, IWindowConfiguration, IPath, IOpenFileRequest, IPathData } from 'vs/platform/windows/common/windows'; +import { URI, UriComponents } 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'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; + +export interface IOpenedWindow { + id: number; + workspace?: IWorkspaceIdentifier; + folderUri?: ISingleFolderWorkspaceIdentifier; + title: string; + filename?: string; + dirty: boolean; +} + +export const enum OpenContext { + + // opening when running from the command line + CLI, + + // macOS only: opening from the dock (also when opening files to a running instance from desktop) + DOCK, + + // opening from the main application window + MENU, + + // opening from a file or folder dialog + DIALOG, + + // opening from the OS's UI + DESKTOP, + + // opening through the API + API +} + +export interface IRunActionInWindowRequest { + id: string; + from: 'menu' | 'touchbar' | 'mouse'; + args?: any[]; +} + +export interface IRunKeybindingInWindowRequest { + userSettingsLabel: string; +} + +export interface IAddFoldersRequest { + foldersToAdd: UriComponents[]; +} + +export interface INativeWindowConfiguration extends IWindowConfiguration, ParsedArgs { + 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 INativeOpenFileRequest extends IOpenFileRequest { + termProgram?: string; + filesToWait?: IPathsToWaitForData; +} + +export interface IPathsToWaitFor extends IPathsToWaitForData { + paths: IPath[]; + waitMarkerFileUri: URI; +} + +export interface IPathsToWaitForData { + paths: IPathData[]; + waitMarkerFileUri: UriComponents; +} export interface INativeOpenWindowOptions extends IOpenWindowOptions { diffMode?: boolean; diff --git a/src/vs/platform/windows/test/node/window.test.ts b/src/vs/platform/windows/test/node/window.test.ts index efda0b1afbc..576d30d134a 100644 --- a/src/vs/platform/windows/test/node/window.test.ts +++ b/src/vs/platform/windows/test/node/window.test.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; -import { IBestWindowOrFolderOptions, IWindowContext, findBestWindowOrFolderForFile } from 'vs/platform/windows/node/window'; -import { OpenContext } from 'vs/platform/windows/common/windows'; +import { IBestWindowOrFolderOptions, IWindowContext, findBestWindowOrFolderForFile, OpenContext } from 'vs/platform/windows/node/window'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index 7e31738058d..911d5a77fc4 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -81,7 +81,7 @@ export interface IWorkspaceFoldersChangeEvent { } export namespace IWorkspace { - export function isIWorkspace(thing: any): thing is IWorkspace { + export function isIWorkspace(thing: unknown): thing is IWorkspace { return thing && typeof thing === 'object' && typeof (thing as IWorkspace).id === 'string' && Array.isArray((thing as IWorkspace).folders); @@ -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; @@ -126,7 +126,7 @@ export interface IWorkspaceFolderData { } export namespace IWorkspaceFolder { - export function isIWorkspaceFolder(thing: any): thing is IWorkspaceFolder { + export function isIWorkspaceFolder(thing: unknown): thing is IWorkspaceFolder { return thing && typeof thing === 'object' && URI.isUri((thing as IWorkspaceFolder).uri) && typeof (thing as IWorkspaceFolder).name === 'string' @@ -144,7 +144,7 @@ export interface IWorkspaceFolder extends IWorkspaceFolderData { export class Workspace implements IWorkspace { - private _foldersMap: TernarySearchTree = TernarySearchTree.forPaths(); + private _foldersMap: TernarySearchTree = TernarySearchTree.forUris(); private _folders!: WorkspaceFolder[]; constructor( @@ -191,13 +191,13 @@ export class Workspace implements IWorkspace { scheme: resource.scheme, authority: resource.authority, path: resource.path - }).toString()) || null; + })) || null; } private updateFoldersMap(): void { - this._foldersMap = TernarySearchTree.forPaths(); + this._foldersMap = TernarySearchTree.forUris(); for (const folder of this.folders) { - this._foldersMap.set(folder.uri.toString(), folder); + this._foldersMap.set(folder.uri, folder); } } diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index efef7f31107..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 { @@ -93,7 +96,7 @@ export function reviveWorkspaceIdentifier(workspace: { id: string, configPath: U return { id: workspace.id, configPath: URI.revive(workspace.configPath) }; } -export function isStoredWorkspaceFolder(thing: any): thing is IStoredWorkspaceFolder { +export function isStoredWorkspaceFolder(thing: unknown): thing is IStoredWorkspaceFolder { return isRawFileWorkspaceFolder(thing) || isRawUriWorkspaceFolder(thing); } @@ -148,11 +151,11 @@ export interface IEnterWorkspaceResult { backupPath?: string; } -export function isSingleFolderWorkspaceIdentifier(obj: any): obj is ISingleFolderWorkspaceIdentifier { +export function isSingleFolderWorkspaceIdentifier(obj: unknown): obj is ISingleFolderWorkspaceIdentifier { return obj instanceof URI; } -export function isWorkspaceIdentifier(obj: any): obj is IWorkspaceIdentifier { +export function isWorkspaceIdentifier(obj: unknown): obj is IWorkspaceIdentifier { const workspaceIdentifier = obj as IWorkspaceIdentifier; return workspaceIdentifier && typeof workspaceIdentifier.id === 'string' && workspaceIdentifier.configPath instanceof URI; @@ -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..25dcc3d140a 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'; @@ -19,11 +18,13 @@ import { isEqual as areResourcesEqual, dirname, originalFSPath, basename } from import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label'; 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 +35,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; @@ -67,7 +68,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa @IStateService private readonly stateService: IStateService, @ILogService private readonly logService: ILogService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService ) { super(); @@ -241,20 +242,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 +406,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..a742475e6d4 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -5,6 +5,7 @@ import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { join, dirname } from 'vs/base/common/path'; import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs'; import { readFileSync, existsSync, mkdirSync } from 'fs'; @@ -75,7 +76,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain readonly onWorkspaceEntered: Event = this._onWorkspaceEntered.event; constructor( - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @ILogService private readonly logService: ILogService, @IBackupMainService private readonly backupMainService: IBackupMainService, @IDialogMainService private readonly dialogMainService: IDialogMainService @@ -175,7 +176,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 70f2bd9bb79..c5a12d23e58 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesService.ts @@ -9,15 +9,17 @@ 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 */> { +export class WorkspacesService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { _serviceBrand: undefined; constructor( @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService + @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, + @IBackupMainService private readonly backupMainService: IBackupMainService ) { } @@ -51,12 +53,7 @@ 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 4f97500f6c2..7b90774ee1f 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -810,7 +810,7 @@ declare module 'vscode' { /** * Creates a reference to a theme icon. - * @param id id of the icon. The avaiable icons are listed in https://microsoft.github.io/vscode-codicons/dist/codicon.html. + * @param id id of the icon. The available icons are listed in https://microsoft.github.io/vscode-codicons/dist/codicon.html. */ constructor(id: string); } @@ -1490,7 +1490,7 @@ declare module 'vscode' { * * @param data The event object. */ - fire(data?: T): void; + fire(data: T): void; /** * Dispose this object and free resources. @@ -1500,7 +1500,7 @@ declare module 'vscode' { /** * A file system watcher notifies about changes to files and folders - * on disk. + * on disk or from other [FileSystemProviders](#FileSystemProvider). * * To get an instance of a `FileSystemWatcher` use * [createFileSystemWatcher](#workspace.createFileSystemWatcher). @@ -1806,7 +1806,7 @@ declare module 'vscode' { placeHolder?: string; /** - * Set to `true` to show a password prompt that will not show the typed value. + * Controls if a password input is shown. Password input hides the typed text. */ password?: boolean; @@ -2020,7 +2020,7 @@ declare module 'vscode' { * Base kind for source actions: `source` * * Source code actions apply to the entire file. They must be explicitly requested and will not show in the - * normal [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) menu. Source actions + * normal [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) menu. Source actions * can be run on save using `editor.codeActionsOnSave` and are also shown in the `source` context menu. */ static readonly Source: CodeActionKind; @@ -2086,7 +2086,7 @@ declare module 'vscode' { /** * Requested kind of actions to return. * - * Actions not of this kind are filtered out before being shown by the lightbulb. + * Actions not of this kind are filtered out before being shown by the [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action). */ readonly only?: CodeActionKind; } @@ -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; @@ -2138,7 +2141,15 @@ declare module 'vscode' { /** * Marks that the code action cannot currently be applied. * - * Disabled code actions will be surfaced in the refactor UI but cannot be applied. + * - Disabled code actions are not shown in automatic [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) + * code action menu. + * + * - Disabled actions are shown as faded out in the code action menu when the user request a more specific type + * 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 an + * error message with `reason` in the editor. */ disabled?: { /** @@ -2163,7 +2174,7 @@ declare module 'vscode' { /** * The code action interface defines the contract between extensions and - * the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature. + * the [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature. * * A code action can be any command that is [known](#commands.getCommands) to the system. */ @@ -2465,6 +2476,55 @@ declare module 'vscode' { provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } + /** + * An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime. + * The result of this evaluation is shown in a tooltip-like widget. + * If only a range is specified, the expression will be extracted from the underlying document. + * An optional expression can be used to override the extracted expression. + * In this case the range is still used to highlight the range in the document. + */ + export class EvaluatableExpression { + + /* + * The range is used to extract the evaluatable expression from the underlying document and to highlight it. + */ + readonly range: Range; + + /* + * If specified the expression overrides the extracted expression. + */ + readonly expression?: string; + + /** + * Creates a new evaluatable expression object. + * + * @param range The range in the underlying document from which the evaluatable expression is extracted. + * @param expression If specified overrides the extracted expression. + */ + constructor(range: Range, expression?: string); + } + + /** + * The evaluatable expression provider interface defines the contract between extensions and + * the debug hover. In this contract the provider returns an evaluatable expression for a given position + * in a document and VS Code evaluates this expression in the active debug session and shows the result in a debug hover. + */ + export interface EvaluatableExpressionProvider { + + /** + * Provide an evaluatable expression for the given document and position. + * VS Code will evaluate this expression in the active debug session and will show the result in the debug hover. + * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression. + * + * @param document The document for which the debug hover is about to appear. + * @param position The line and character position in the document where the debug hover is about to appear. + * @param token A cancellation token. + * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + } + /** * A document highlight kind. */ @@ -2850,6 +2910,34 @@ declare module 'vscode' { constructor(range: Range, newText: string); } + /** + * Additional data for entries of a workspace edit. Supports to label entries and marks entries + * as needing confirmation by the user. The editor groups edits with equal labels into tree nodes, + * for instance all edits labelled with "Changes in Strings" would be a tree node. + */ + export interface WorkspaceEditEntryMetadata { + + /** + * A flag which indicates that user confirmation is needed. + */ + needsConfirmation: boolean; + + /** + * A human-readable string which is rendered prominent. + */ + label: string; + + /** + * A human-readable string which is rendered less prominent on the same line. + */ + description?: string; + + /** + * The icon path or [ThemeIcon](#ThemeIcon) for the edit. + */ + iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; + } + /** * A workspace edit is a collection of textual and files changes for * multiple resources and documents. @@ -2869,8 +2957,9 @@ declare module 'vscode' { * @param uri A resource identifier. * @param range A range. * @param newText A string. + * @param metadata Optional metadata for the entry. */ - replace(uri: Uri, range: Range, newText: string): void; + replace(uri: Uri, range: Range, newText: string, metadata?: WorkspaceEditEntryMetadata): void; /** * Insert the given text at the given position. @@ -2878,16 +2967,18 @@ declare module 'vscode' { * @param uri A resource identifier. * @param position A position. * @param newText A string. + * @param metadata Optional metadata for the entry. */ - insert(uri: Uri, position: Position, newText: string): void; + insert(uri: Uri, position: Position, newText: string, metadata?: WorkspaceEditEntryMetadata): void; /** * Delete the text at the given range. * * @param uri A resource identifier. * @param range A range. + * @param metadata Optional metadata for the entry. */ - delete(uri: Uri, range: Range): void; + delete(uri: Uri, range: Range, metadata?: WorkspaceEditEntryMetadata): void; /** * Check if a text edit for a resource exists. @@ -2919,15 +3010,17 @@ declare module 'vscode' { * @param uri Uri of the new file.. * @param options Defines if an existing file should be overwritten or be * ignored. When overwrite and ignoreIfExists are both set overwrite wins. + * @param metadata Optional metadata for the entry. */ - createFile(uri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void; + createFile(uri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void; /** * Delete a file or folder. * * @param uri The uri of the file that is to be deleted. + * @param metadata Optional metadata for the entry. */ - deleteFile(uri: Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }): void; + deleteFile(uri: Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void; /** * Rename a file or folder. @@ -2936,8 +3029,9 @@ declare module 'vscode' { * @param newUri The new location. * @param options Defines if existing files should be overwritten or be * ignored. When overwrite and ignoreIfExists are both set overwrite wins. + * @param metadata Optional metadata for the entry. */ - renameFile(oldUri: Uri, newUri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void; + renameFile(oldUri: Uri, newUri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void; /** @@ -3056,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. */ @@ -4594,7 +4915,18 @@ declare module 'vscode' { * A code or identifier for this diagnostic. * Should be used for later processing, e.g. when providing [code actions](#CodeActionContext). */ - code?: string | number; + code?: string | number | { + /** + * A code or identifier for this diagnostic. + * Should be used for later processing, e.g. when providing [code actions](#CodeActionContext). + */ + value: string | number; + + /** + * A target URI to open with more information about the diagnostic error. + */ + target: Uri; + }; /** * An array of related diagnostic information, e.g. when symbol-names within @@ -4873,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. @@ -5914,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; } /** @@ -6090,7 +6434,7 @@ declare module 'vscode' { * with files from the local disk as well as files from remote places, like the * remote extension host or ftp-servers. * - * *Note* that an instance of this interface is avaiable as [`workspace.fs`](#workspace.fs). + * *Note* that an instance of this interface is available as [`workspace.fs`](#workspace.fs). */ export interface FileSystem { @@ -6222,7 +6566,7 @@ declare module 'vscode' { } /** - * A webview displays html content, like an iframe. + * Displays html content, similarly to an iframe. */ export interface Webview { /** @@ -6231,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; @@ -6331,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; @@ -6448,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. */ @@ -7230,6 +7622,14 @@ declare module 'vscode' { */ export function createTerminal(options: ExtensionTerminalOptions): Terminal; + /** + * Register a [TerminalLinkHandler](#TerminalLinkHandler) that can be used to intercept and + * handle links that are activated within terminals. + * @param handler The link handler being registered. + * @return A disposable that unregisters the link handler. + */ + export function registerTerminalLinkHandler(handler: TerminalLinkHandler): Disposable; + /** * Register a [TreeDataProvider](#TreeDataProvider) for the view contributed using the extension point `views`. * This will allow you to contribute data to the [TreeView](#TreeView) and update if the data changes. @@ -7284,6 +7684,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; } /** @@ -7462,7 +7877,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; @@ -7476,7 +7891,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; @@ -7775,6 +8190,19 @@ declare module 'vscode' { readonly code: number | undefined; } + /** + * Describes how to handle terminal links. + */ + 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; + } + /** * A location in the editor at which progress information can be shown. It depends on the * location how progress is visually represented. @@ -7806,7 +8234,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 @@ -9080,6 +9508,18 @@ declare module 'vscode' { */ export function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable; + /** + * Register a provider that locates evaluatable expressions in text documents. + * VS Code will evaluate the expression in the active debug session and will show the result in the debug hover. + * + * If multiple providers are registered for a language an arbitrary provider will be used. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An evaluatable expression provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable; + /** * Register a document highlight provider. * @@ -9145,6 +9585,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 deb1bae0050..72b565efc75 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -20,7 +20,7 @@ declare module 'vscode' { export interface AuthenticationSession { id: string; - accessToken(): Promise; + getAccessToken(): Thenable; accountName: string; scopes: string[] } @@ -35,36 +35,61 @@ 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. */ - getSessions(): Promise>; + getSessions(): Thenable>; /** * Prompts a user to login. */ - login(scopes: string[]): Promise; - logout(sessionId: string): Promise; + login(scopes: string[]): Thenable; + logout(sessionId: string): Thenable; } export namespace authentication { @@ -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 @@ -876,67 +714,7 @@ declare module 'vscode' { //#endregion - //#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084 - - /** - * An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime. - * The result of this evaluation is shown in a tooltip-like widget. - * If only a range is specified, the expression will be extracted from the underlying document. - * An optional expression can be used to override the extracted expression. - * In this case the range is still used to highlight the range in the document. - */ - export class EvaluatableExpression { - /* - * The range is used to extract the evaluatable expression from the underlying document and to highlight it. - */ - readonly range: Range; - /* - * If specified the expression overrides the extracted expression. - */ - readonly expression?: string; - - /** - * Creates a new evaluatable expression object. - * - * @param range The range in the underlying document from which the evaluatable expression is extracted. - * @param expression If specified overrides the extracted expression. - */ - constructor(range: Range, expression?: string); - } - - /** - * The evaluatable expression provider interface defines the contract between extensions and - * the debug hover. - */ - export interface EvaluatableExpressionProvider { - - /** - * Provide an evaluatable expression for the given document and position. - * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression. - * - * @param document The document in which the command was invoked. - * @param position The position where the command was invoked. - * @param token A cancellation token. - * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be - * signaled by returning `undefined` or `null`. - */ - provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - } - - export namespace languages { - /** - * Register a provider that locates evaluatable expressions in text documents. - * - * If multiple providers are registered for a language an arbitrary provider will be used. - * - * @param selector A selector that defines the documents this provider is applicable to. - * @param provider An evaluatable expression provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable; - } - - // deprecated + //#region deprecated debug API export interface DebugConfigurationProvider { /** @@ -951,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, @@ -965,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; } @@ -1099,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. */ @@ -1117,6 +895,106 @@ declare module 'vscode' { //#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 { @@ -1245,67 +1123,183 @@ declare module 'vscode' { //#endregion - //#region Custom editors: https://github.com/microsoft/vscode/issues/77131 + //#region OnTypeRename: https://github.com/microsoft/vscode/issues/88424 /** - * Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard - * editor events such as `undo` or `save`. - * - * @param EditType Type of edits. + * The rename provider interface defines the contract between extensions and + * the live-rename feature. */ - interface WebviewCustomEditorEditingDelegate { + export interface OnTypeRenameProvider { /** - * Save a resource. + * Provide a list of ranges that can be live renamed together. * - * @param resource Resource being saved. - * - * @return Thenable signaling that the save has completed. + * @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. */ - save(resource: Uri): Thenable; + 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 + + /** + * 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. + */ + interface CustomDocument { + /** + * The associated uri for this document. + */ + readonly uri: Uri; /** - * Save an existing resource at a new path. + * Dispose of the custom document. + * + * This is invoked by VS Code when there are no more references to a given `CustomDocument` (for example when + * all editors associated with the document have been closed.) + */ + dispose(): void; + } + + /** + * Event triggered by extensions to signal to VS Code that an edit has occurred on an [`EditableCustomDocument`](#EditableCustomDocument). + */ + interface CustomDocumentEditEvent { + /** + * Undo the edit operation. + * + * This is invoked by VS Code when the user triggers an undo. + */ + undo(): Thenable | void; + + /** + * Redo the edit operation. + * + * This is invoked by VS Code when the user triggers a redo. + */ + redo(): Thenable | void; + + /** + * Display name describing the edit. + * + * This is shown in the UI to users. + */ + readonly label?: string; + } + + /** + * A backup for an [`EditableCustomDocument`](#EditableCustomDocument). + */ + interface CustomDocumentBackup { + /** + * Unique identifier for the backup. + * + * This id is passed back to your extension in `openCustomDocument` when opening a custom editor from a backup. + */ + readonly backupId: string; + + /** + * Dispose of the current backup. + * + * This is called by VS Code when it is clear the current backup, such as when a new backup is made or when the + * file is saved. + */ + dispose(): void; + } + + /** + * Represents an editable custom document used by a [`CustomEditorProvider`](#CustomEditorProvider). + * + * `EditableCustomDocument` is how custom editors hook into standard VS Code operations such as save and undo. The + * document is also how custom editors notify VS Code that an edit has taken place. + */ + interface EditableCustomDocument extends CustomDocument { + /** + * Save the resource for a custom editor. + * + * 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 implementer 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 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(cancellation: CancellationToken): Thenable; + + /** + * 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 implementer must persist the custom editor to `targetResource`. The + * existing editor will remain open after `saveAs` completes. * - * @param resource Resource being saved. * @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(resource: Uri, targetResource: Uri): Thenable; + saveAs(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. + * + * Firing this will cause VS Code to mark the editors as being dirty. This also allows the user to then undo and + * redo the edit in the custom editor. */ - // TODO@matt - // eslint-disable-next-line vscode-dts-event-naming - readonly onEdit: Event<{ readonly resource: Uri, readonly edit: EditType }>; + readonly onDidEdit: Event; /** - * Apply a set of edits. + * Revert a custom editor to its last saved state. * - * Note that is not invoked when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit. + * 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). * - * @param resource Resource being edited. - * @param edit Array of edits. Sorted from oldest to most recent. + * To implement `revert`, the implementer 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 cancellation Token that signals the revert is no longer required. * * @return Thenable signaling that the change has completed. */ - applyEdits(resource: Uri, edits: readonly EditType[]): Thenable; + revert(cancellation: CancellationToken): Thenable; /** - * Undo a set of edits. - * - * This is triggered when a user undoes an edit or when revert is called on a file. - * - * @param resource Resource being edited. - * @param edit Array of edits. Sorted from most recent to oldest. - * - * @return Thenable signaling that the change has completed. - */ - undoEdits(resource: Uri, edits: readonly EditType[]): Thenable; - - /** - * Back up `resource` in its current state. + * Back up the resource in its current state. * * Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in * its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in @@ -1317,59 +1311,111 @@ 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 resource The resource to back up. * @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?(resource: Uri, cancellation: CancellationToken): Thenable; + backup(cancellation: CancellationToken): Thenable; } - export interface WebviewCustomEditorProvider { + /** + * Additional information about the opening custom document. + */ + interface OpenCustomDocumentContext { /** - * Resolve a webview editor for a given resource. + * The id of the backup to restore the document from or `undefined` if there is no backup. * - * To resolve a webview editor, a 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 resource Resource being resolved. - * @param webview Webview being resolved. The provider should take ownership of this webview. - * - * @return Thenable indicating that the webview editor has been resolved. + * If this is provided, your extension should restore the editor from the backup instead of reading the file + * the user's workspace. */ - resolveWebviewEditor( - resource: Uri, - webview: WebviewPanel, - ): Thenable; + readonly backupId?: string; + } + + /** + * Provider for custom editors that use a custom document 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 this type of custom editor when dealing with binary files or more complex scenarios. For simple + * text based documents, use [`CustomTextEditorProvider`](#CustomTextEditorProvider) instead. + * + * @param DocumentType Type of the custom document returned by this provider. + */ + export interface CustomEditorProvider { /** - * Controls the editing functionality of a webview editor. This allows the webview editor to hook into standard - * editor events such as `undo` or `save`. + * Create a new document for a given resource. * - * WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact - * with readonly editors, but these editors will not integrate with VS Code's standard editor functionality. + * `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 openContext Additional information about the opening custom document. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return The custom document. */ - readonly editingDelegate?: WebviewCustomEditorEditingDelegate; + openCustomDocument(uri: Uri, openContext: OpenCustomDocumentContext, token: CancellationToken): Thenable | DocumentType; + + /** + * 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. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return Optional thenable indicating that the custom editor has been resolved. + */ + resolveCustomEditor(document: DocumentType, webviewPanel: WebviewPanel, token: CancellationToken): Thenable | void; } namespace window { /** - * Register a new provider for webview editors of a given type. - * - * @param viewType Type of the webview editor provider. - * @param provider Resolves webview editors. - * @param options Content settings for a webview panels the provider is given. - * - * @return Disposable that unregisters the `WebviewCustomEditorProvider`. + * Temporary overload for `registerCustomEditorProvider` that takes a `CustomEditorProvider`. */ - export function registerWebviewCustomEditorProvider( + export function registerCustomEditorProvider2( viewType: string, - provider: WebviewCustomEditorProvider, - options?: 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 @@ -1396,6 +1442,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: CellDisplayOutput, 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 /** @@ -1450,9 +1690,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`. @@ -1468,71 +1708,6 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/77728 - - /** - * Additional data for entries of a workspace edit. Supports to label entries and marks entries - * as needing confirmation by the user. The editor groups edits with equal labels into tree nodes, - * for instance all edits labelled with "Changes in Strings" would be a tree node. - */ - export interface WorkspaceEditMetadata { - - /** - * A flag which indicates that user confirmation is needed. - */ - needsConfirmation: boolean; - - /** - * A human-readable string which is rendered prominent. - */ - label: string; - - /** - * A human-readable string which is rendered less prominent on the same line. - */ - description?: string; - - /** - * The icon path or [ThemeIcon](#ThemeIcon) for the edit. - */ - iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; - } - - export interface WorkspaceEdit { - - insert(uri: Uri, position: Position, newText: string, metadata?: WorkspaceEditMetadata): void; - delete(uri: Uri, range: Range, metadata?: WorkspaceEditMetadata): void; - replace(uri: Uri, range: Range, newText: string, metadata?: WorkspaceEditMetadata): void; - - createFile(uri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }, metadata?: WorkspaceEditMetadata): void; - deleteFile(uri: Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }, metadata?: WorkspaceEditMetadata): void; - renameFile(oldUri: Uri, newUri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }, metadata?: WorkspaceEditMetadata): void; - } - - //#endregion - - //#region Diagnostic links https://github.com/microsoft/vscode/issues/11847 - - export interface Diagnostic { - /** - * Will be merged into `Diagnostic#code` - */ - code2?: { - /** - * A code or identifier for this diagnostic. - * Should be used for later processing, e.g. when providing [code actions](#CodeActionContext). - */ - value: string | number; - - /** - * A target URI to open with more information about the diagnostic error. - */ - target: Uri; - } - } - - //#endregion - //#region eamodio - timeline: https://github.com/microsoft/vscode/issues/84297 export class TimelineItem { @@ -1547,12 +1722,9 @@ declare module 'vscode' { label: string; /** - * Optional id for the timeline item. - */ - /** - * Optional id for the timeline item that has to be unique across your timeline source. + * Optional id for the timeline item. It must be unique across all the timeline items provided by this source. * - * If not provided, an id is generated using the timeline item's label. + * If not provided, an id is generated using the timeline item's timestamp. */ id?: string; @@ -1606,9 +1778,41 @@ declare module 'vscode' { export interface TimelineChangeEvent { /** * The [uri](#Uri) of the resource for which the timeline changed. - * If the [uri](#Uri) is `undefined` that signals that the timeline source for all resources changed. */ - uri?: Uri; + uri: Uri; + + /** + * A flag which indicates whether the entire timeline should be reset. + */ + reset?: boolean; + } + + export interface Timeline { + readonly paging?: { + /** + * 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 cursor: string | undefined; + } + + /** + * An array of [timeline items](#TimelineItem). + */ + readonly items: readonly TimelineItem[]; + } + + export interface TimelineOptions { + /** + * A provider-defined cursor specifying the starting point of the timeline items that should be returned. + */ + cursor?: string; + + /** + * 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. + */ + limit?: number | { timestamp: number; id?: string }; } export interface TimelineProvider { @@ -1616,27 +1820,28 @@ declare module 'vscode' { * An optional event to signal that the timeline for a source has changed. * To signal that the timeline for all resources (uris) has changed, do not pass any argument or pass `undefined`. */ - onDidChange?: Event; + onDidChange?: Event; /** * An identifier of the source of the timeline items. This can be used to filter sources. */ - id: string; + readonly id: string; /** * A human-readable string describing the source of the timeline items. This can be used as the display label when filtering sources. */ - label: string; + readonly label: string; /** * Provide [timeline items](#TimelineItem) for a [Uri](#Uri). * * @param uri The [uri](#Uri) of the file to provide the timeline for. + * @param options A set of options to determine how results should be returned. * @param token A cancellation token. - * @return An array of timeline items or a thenable that resolves to such. The lack of a result + * @return The [timeline result](#TimelineResult) or a thenable that resolves to such. The lack of a result * can be signaled by returning `undefined`, `null`, or an empty array. */ - provideTimeline(uri: Uri, token: CancellationToken): ProviderResult; + provideTimeline(uri: Uri, options: TimelineOptions, token: CancellationToken): ProviderResult; } export namespace workspace { @@ -1656,31 +1861,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 { @@ -1689,9 +1869,10 @@ declare module 'vscode' { * * The documentation is shown in the code actions menu if either: * - * - Code actions of `kind` are requested by VS Code. Note that in this case, we always pick the most specific - * documentation. For example, if documentation for both `Refactor` and `RefactorExtract` is provided, and we - * request code actions for `RefactorExtract`, we prefer the more specific documentation for `RefactorExtract`. + * - Code actions of `kind` are requested by VS Code. In this case, VS Code will show the documentation that + * most closely matches the requested code action kind. For example, if a provider has documentation for + * both `Refactor` and `RefactorExtract`, when the user requests code actions for `RefactorExtract`, + * VS Code will use the documentation for `RefactorExtract` intead of the documentation for `Refactor`. * * - Any code actions of `kind` are returned by the provider. */ @@ -1699,4 +1880,102 @@ declare module 'vscode' { } //#endregion + + //#region Dialog title: https://github.com/microsoft/vscode/issues/82871 + + /** + * Options to configure the behaviour of a file open dialog. + * + * * Note 1: A dialog can select files, folders, or both. This is not true for Windows + * which enforces to open either files or folder, but *not both*. + * * Note 2: Explicitly setting `canSelectFiles` and `canSelectFolders` to `false` is futile + * and the editor then silently adjusts the options to select files. + */ + export interface OpenDialogOptions { + /** + * Dialog title. + * + * Depending on the underlying operating system this parameter might be ignored, since some + * systems do not present title on open dialogs. + */ + title?: string; + } + + /** + * Options to configure the behaviour of a file save dialog. + */ + export interface SaveDialogOptions { + /** + * Dialog title. + * + * Depending on the underlying operating system this parameter might be ignored, since some + * systems do not present title on save dialogs. + */ + title?: string; + } + + //#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 segments. + * + * - Note 1: `joinPath` only affects the path component + * and all other components (scheme, authority, query, and fragment) are + * left as they are. + * - Note 2: The base uri must have a path; an error is thrown otherwise. + * + * The path segments are normalized in the following ways: + * - sequences of path separators (`/` or `\`) are replaced with a single separator + * - for `file`-uris on windows, the backslash-character (`\`) is considered a path-separator + * - the `..`-segment denotes the parent segment, the `.` denotes the current segement + * - paths have a root which always remains, for instance on windows drive-letters are roots + * so that is true: `joinPath(Uri.file('file:///c:/root'), '../../other').fsPath === 'c:/other'` + * + * @param base An uri. Must have a path. + * @param pathSegments One more more path fragments + * @returns A new uri which path is joined with the given fragments + */ + export function joinPath(base: Uri, ...pathSegments: 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 311d89790db..c0950638734 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,36 +12,241 @@ 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'; +import { INotificationService } from 'vs/platform/notification/common/notification'; + +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[], + private readonly notificationService: INotificationService + ) { + 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 => { return { id: session.id, accountName: session.accountName, - accessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) + getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) }; }); } + 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 { id: session.id, accountName: session.accountName, - accessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) + getAccessToken: () => this._proxy.$getSessionAccessToken(this.id, session.id) }; }); } - logout(accountId: string): Promise { - return this._proxy.$logout(this.id, accountId); + async logout(sessionId: string): Promise { + await this._proxy.$logout(this.id, sessionId); + this.notificationService.info(nls.localize('signedOut', "Successfully signed out.")); + } + + dispose(): void { + super.dispose(); + this._sessionMenuItems.forEach(item => item.forEach(d => d.dispose())); + this._sessionMenuItems.clear(); } } @@ -53,14 +258,17 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu extHostContext: IExtHostContext, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IDialogService private readonly dialogService: IDialogService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @INotificationService private readonly notificationService: INotificationService ) { super(); 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.notificationService); this.authenticationService.registerAuthenticationProvider(id, provider); } @@ -68,55 +276,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) { + 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 } = await this.dialogService.show( Severity.Info, - nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information from {1}.", extensionName, providerName), - [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow"), nls.localize('alwaysAllow', "Always Allow"),], - { cancelId: 0 } + 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 + } ); - switch (choice) { - case 1/** Allow */: - return true; - case 2 /** Always Allow */: - this.storageService.store(`${extensionId}-${providerId}`, 'true', StorageScope.GLOBAL); - return true; - default: - return false; + const allow = choice === 1; + 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 true; - } - + 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('neverAgain', "Don't Show Again")], - { cancelId: 0 } + [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")], + { + cancelId: 0 + } ); - switch (choice) { - case 1/** Allow */: - return true; - case 2 /** Always Allow */: - this.storageService.store(`${extensionId}-${providerId}`, 'true', StorageScope.GLOBAL); - return true; - default: - return false; - } + 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/mainThreadDialogs.ts b/src/vs/workbench/api/browser/mainThreadDialogs.ts index 94ab09b7eba..79c26c34570 100644 --- a/src/vs/workbench/api/browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/browser/mainThreadDialogs.ts @@ -37,7 +37,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders), canSelectFolders: options.canSelectFolders, canSelectMany: options.canSelectMany, - defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined + defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined, + title: options.title || undefined }; if (options.filters) { result.filters = []; @@ -49,7 +50,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions { const result: ISaveDialogOptions = { defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined, - saveLabel: options.saveLabel || undefined + saveLabel: options.saveLabel || undefined, + title: options.title || undefined }; if (options.filters) { result.filters = []; 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 4de14d85d61..1cb9c614eb4 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -10,11 +10,11 @@ import { RenderLineNumbersType, TextEditorCursorStyle, cursorStyleToString, Edit import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { IDecorationOptions, ScrollType } from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, ISingleEditOperation, ITextModel, ITextModelUpdateOptions } from 'vs/editor/common/model'; +import { ISingleEditOperation, ITextModel, ITextModelUpdateOptions, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; 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/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index 38f3b5fdca9..d88424433fb 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -8,13 +8,13 @@ import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/c import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService } from 'vs/platform/progress/common/progress'; import { localize } from 'vs/nls'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; @extHostCustomer export class MainThreadFileSystemEventService { @@ -28,6 +28,7 @@ export class MainThreadFileSystemEventService { @IProgressService progressService: IProgressService, @IConfigurationService configService: IConfigurationService, @ILogService logService: ILogService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService ) { const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); @@ -38,7 +39,7 @@ export class MainThreadFileSystemEventService { changed: [], deleted: [] }; - this._listener.add(fileService.onFileChanges(event => { + this._listener.add(fileService.onDidFilesChange(event => { for (let change of event.changes) { switch (change.type) { case FileChangeType.ADDED: @@ -61,45 +62,15 @@ export class MainThreadFileSystemEventService { // BEFORE file operation - const messages = new Map(); - messages.set(FileOperation.CREATE, localize('msg-create', "Running 'File Create' participants...")); - messages.set(FileOperation.DELETE, localize('msg-delete', "Running 'File Delete' participants...")); - messages.set(FileOperation.MOVE, localize('msg-rename', "Running 'File Rename' participants...")); - - - this._listener.add(textFileService.onWillRunOperation(e => { - - const timeout = configService.getValue('files.participants.timeout'); - if (timeout <= 0) { - return; // disabled + workingCopyFileService.addFileOperationParticipant({ + participate: (target, source, operation, progress, timeout, token) => { + return proxy.$onWillRunFileOperation(operation, target, source, timeout, token); } - - const p = progressService.withProgress({ location: ProgressLocation.Window }, progress => { - - progress.report({ message: messages.get(e.operation) }); - - return new Promise((resolve, reject) => { - - const cts = new CancellationTokenSource(); - - const timeoutHandle = setTimeout(() => { - logService.trace('CANCELLED file participants because of timeout', timeout, e.target, e.operation); - cts.cancel(); - reject(new Error('timeout')); - }, timeout); - - proxy.$onWillRunFileOperation(e.operation, e.target, e.source, timeout, cts.token) - .then(resolve, reject) - .finally(() => clearTimeout(timeoutHandle)); - }); - - }); - - e.waitUntil(p); - })); + }); // AFTER file operation - this._listener.add(textFileService.onDidRunOperation(e => proxy.$onDidRunFileOperation(e.operation, e.target, e.source))); + this._listener.add(textFileService.onDidCreateTextFile(e => proxy.$onDidRunFileOperation(FileOperation.CREATE, e.resource, undefined))); + this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.target, e.source))); } dispose(): void { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index de204a88dbd..0f52d714290 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ 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'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -19,9 +19,9 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import * as callh from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +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 { @@ -36,6 +36,34 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures); this._modeService = modeService; + + if (this._modeService) { + const updateAllWordDefinitions = () => { + const langWordPairs = LanguageConfigurationRegistry.getWordDefinitions(); + let wordDefinitionDtos: ILanguageWordDefinitionDto[] = []; + for (const [languageId, wordDefinition] of langWordPairs) { + const language = this._modeService.getLanguageIdentifier(languageId); + if (!language) { + continue; + } + wordDefinitionDtos.push({ + languageId: language.language, + regexSource: wordDefinition.source, + regexFlags: wordDefinition.flags + }); + } + this._proxy.$setWordDefinitions(wordDefinitionDtos); + }; + LanguageConfigurationRegistry.onDidChange((e) => { + const wordDefinition = LanguageConfigurationRegistry.getWordDefinition(e.languageIdentifier.id); + this._proxy.$setWordDefinitions([{ + languageId: e.languageIdentifier.language, + regexSource: wordDefinition.source, + regexFlags: wordDefinition.flags + }]); + }); + updateAllWordDefinitions(); + } } dispose(): void { @@ -233,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 { @@ -245,7 +285,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- quick fix - $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto): void { + $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string): void { this._registrations.set(handle, modes.CodeActionProviderRegistry.register(selector, { provideCodeActions: async (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { const listDto = await this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token); @@ -262,7 +302,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; }, providedCodeActionKinds: metadata.providedKinds, - documentation: metadata.documentation + documentation: metadata.documentation, + displayName })); } @@ -338,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 { @@ -632,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/mainThreadQuickOpen.ts b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts index 30e3d7bca23..e1a8fdbd11a 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts @@ -85,7 +85,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { // ---- input - $input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise { + $input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise { const inputOptions: IInputOptions = Object.create(null); if (options) { diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index e75ac0b4da3..158de62f0c9 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -71,8 +71,8 @@ class MainThreadSCMResource implements ISCMResource { public decorations: ISCMResourceDecorations ) { } - open(): Promise { - return this.proxy.$executeResourceCommand(this.sourceControlHandle, this.groupHandle, this.handle); + open(preserveFocus: boolean): Promise { + return this.proxy.$executeResourceCommand(this.sourceControlHandle, this.groupHandle, this.handle, preserveFocus); } toJSON(): any { diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 288a349b183..ba5c1708f26 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -3,304 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IdleValue, raceCancellation } from 'vs/base/common/async'; -import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; -import * as strings from 'vs/base/common/strings'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { ITextModel } from 'vs/editor/common/model'; -import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; -import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; -import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService, ProgressLocation, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; +import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ISaveParticipant, 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 { ILabelService } from 'vs/platform/label/common/label'; import { canceled } from 'vs/base/common/errors'; +import { IDisposable } from 'vs/base/common/lifecycle'; -export interface ISaveParticipantParticipant { - participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise; -} - -class TrimWhitespaceParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService - ) { - // Nothing - } - - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { - this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); - } - } - - private doTrimTrailingWhitespace(model: ITextModel, isAutoSaved: boolean): void { - let prevSelection: Selection[] = []; - let cursors: Position[] = []; - - const editor = findEditor(model, this.codeEditorService); - if (editor) { - // Find `prevSelection` in any case do ensure a good undo stack when pushing the edit - // Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump - prevSelection = editor.getSelections(); - if (isAutoSaved) { - cursors = prevSelection.map(s => s.getPosition()); - const snippetsRange = SnippetController2.get(editor).getSessionEnclosingRange(); - if (snippetsRange) { - for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) { - cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber))); - } - } - } - } - - const ops = trimTrailingWhitespace(model, cursors); - if (!ops.length) { - return; // Nothing to do - } - - model.pushEditOperations(prevSelection, ops, (_edits) => prevSelection); - } -} - -function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null { - let candidate: IActiveCodeEditor | null = null; - - if (model.isAttachedToEditor()) { - for (const editor of codeEditorService.listCodeEditors()) { - if (editor.hasModel() && editor.getModel() === model) { - if (editor.hasTextFocus()) { - return editor; // favour focused editor if there are multiple - } - - candidate = editor; - } - } - } - - return candidate; -} - -export class FinalNewLineParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService - ) { - // Nothing - } - - async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { - this.doInsertFinalNewLine(model.textEditorModel); - } - } - - private doInsertFinalNewLine(model: ITextModel): void { - const lineCount = model.getLineCount(); - const lastLine = model.getLineContent(lineCount); - const lastLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(lastLine) === -1; - - if (!lineCount || lastLineIsEmptyOrWhitespace) { - return; - } - - const edits = [EditOperation.insert(new Position(lineCount, model.getLineMaxColumn(lineCount)), model.getEOL())]; - const editor = findEditor(model, this.codeEditorService); - if (editor) { - editor.executeEdits('insertFinalNewLine', edits, editor.getSelections()); - } else { - model.pushEditOperations([], edits, () => null); - } - } -} - -export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService - ) { - // Nothing - } - - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { - this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); - } - } - - /** - * returns 0 if the entire file is empty or whitespace only - */ - private findLastLineWithContent(model: ITextModel): number { - for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) { - const lineContent = model.getLineContent(lineNumber); - if (strings.lastNonWhitespaceIndex(lineContent) !== -1) { - // this line has content - return lineNumber; - } - } - // no line has content - return 0; - } - - private doTrimFinalNewLines(model: ITextModel, isAutoSaved: boolean): void { - const lineCount = model.getLineCount(); - - // Do not insert new line if file does not end with new line - if (lineCount === 1) { - return; - } - - let prevSelection: Selection[] = []; - let cannotTouchLineNumber = 0; - const editor = findEditor(model, this.codeEditorService); - if (editor) { - prevSelection = editor.getSelections(); - if (isAutoSaved) { - for (let i = 0, len = prevSelection.length; i < len; i++) { - const positionLineNumber = prevSelection[i].positionLineNumber; - if (positionLineNumber > cannotTouchLineNumber) { - cannotTouchLineNumber = positionLineNumber; - } - } - } - } - - const lastLineNumberWithContent = this.findLastLineWithContent(model); - const deleteFromLineNumber = Math.max(lastLineNumberWithContent + 1, cannotTouchLineNumber + 1); - const deletionRange = model.validateRange(new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount))); - - if (deletionRange.isEmpty()) { - return; - } - - model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], _edits => prevSelection); - - if (editor) { - editor.setSelections(prevSelection); - } - } -} - -class FormatOnSaveParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, - @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - ) { - // 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 }; - - if (env.reason === SaveReason.AUTO || !this._configurationService.getValue('editor.formatOnSave', overrides)) { - return undefined; - } - - progress.report({ message: localize('formatting', "Formatting") }); - const editorOrModel = findEditor(model, this._codeEditorService) || model; - await this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, token); - } -} - -class CodeActionOnSaveParticipant implements ISaveParticipantParticipant { - - constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - ) { } - - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { - if (env.reason === SaveReason.AUTO) { - return undefined; - } - const model = editorModel.textEditorModel; - - const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource }; - const setting = this._configurationService.getValue<{ [kind: string]: boolean }>('editor.codeActionsOnSave', settingsOverrides); - if (!setting) { - return undefined; - } - - const codeActionsOnSave = Object.keys(setting) - .filter(x => setting[x]).map(x => new CodeActionKind(x)) - .sort((a, b) => { - if (CodeActionKind.SourceFixAll.contains(a)) { - if (CodeActionKind.SourceFixAll.contains(b)) { - return 0; - } - return -1; - } - if (CodeActionKind.SourceFixAll.contains(b)) { - return 1; - } - return 0; - }); - - if (!codeActionsOnSave.length) { - return undefined; - } - - const excludedActions = 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, token); - } - - private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise { - for (const codeActionKind of codeActionsOnSave) { - const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token); - try { - await this.applyCodeActions(actionsToRun.validActions); - } catch { - // Failure to apply a code action should not block other on save actions - } finally { - actionsToRun.dispose(); - } - } - } - - private async applyCodeActions(actionsToRun: readonly CodeAction[]) { - for (const action of actionsToRun) { - await this._instantiationService.invokeFunction(applyCodeAction, action); - } - } - - private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) { - return getCodeActions(model, model.getFullModelRange(), { - type: CodeActionTriggerType.Auto, - filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true }, - }, token); - } -} - -class ExtHostSaveParticipant implements ISaveParticipantParticipant { +class ExtHostSaveParticipant implements ITextFileSaveParticipant { private readonly _proxy: ExtHostDocumentSaveParticipantShape; @@ -308,9 +23,9 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { 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; @@ -336,65 +51,19 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { // The save participant can change a model before its saved to support various scenarios like trimming trailing whitespace @extHostCustomer -export class SaveParticipant implements ISaveParticipant { +export class SaveParticipant { - private readonly _saveParticipants: IdleValue; + private _saveParticipantDisposable: IDisposable; constructor( extHostContext: IExtHostContext, @IInstantiationService instantiationService: IInstantiationService, - @IProgressService private readonly _progressService: IProgressService, - @ILogService private readonly _logService: ILogService, - @ILabelService private readonly _labelService: ILabelService, @ITextFileService private readonly _textFileService: ITextFileService ) { - this._saveParticipants = new IdleValue(() => [ - instantiationService.createInstance(TrimWhitespaceParticipant), - instantiationService.createInstance(CodeActionOnSaveParticipant), - instantiationService.createInstance(FormatOnSaveParticipant), - instantiationService.createInstance(FinalNewLineParticipant), - instantiationService.createInstance(TrimFinalNewLinesParticipant), - instantiationService.createInstance(ExtHostSaveParticipant, extHostContext), - ]); - // Set as save participant for all text files - this._textFileService.saveParticipant = this; + this._saveParticipantDisposable = this._textFileService.files.addSaveParticipant(instantiationService.createInstance(ExtHostSaveParticipant, extHostContext)); } dispose(): void { - this._textFileService.saveParticipant = undefined; - this._saveParticipants.dispose(); - } - - async participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { - - const cts = new CancellationTokenSource(token); - - return this._progressService.withProgress({ - title: localize('saveParticipants', "Running Save Participants for '{0}'", this._labelService.getUriLabel(model.resource, { relative: true })), - location: ProgressLocation.Notification, - cancellable: true, - delay: model.isDirty() ? 3000 : 5000 - }, async progress => { - // undoStop before participation - model.textEditorModel.pushStackElement(); - - for (let p of this._saveParticipants.getValue()) { - if (cts.token.isCancellationRequested) { - break; - } - try { - const promise = p.participate(model, context, progress, cts.token); - await raceCancellation(promise, cts.token); - } catch (err) { - this._logService.warn(err); - } - } - - // undoStop after participation - model.textEditorModel.pushStackElement(); - }, () => { - // user cancel - cts.dispose(true); - }); + this._saveParticipantDisposable.dispose(); } } diff --git a/src/vs/workbench/api/browser/mainThreadSearch.ts b/src/vs/workbench/api/browser/mainThreadSearch.ts index 426f361e786..9d2fc19ab3a 100644 --- a/src/vs/workbench/api/browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/browser/mainThreadSearch.ts @@ -80,10 +80,14 @@ class SearchOperation { } addMatch(match: IFileMatch): void { - if (this.matches.has(match.resource.toString())) { - // Merge with previous IFileMatches + const existingMatch = this.matches.get(match.resource.toString()); + if (existingMatch) { // TODO@rob clean up text/file result types - this.matches.get(match.resource.toString())!.results!.push(...match.results!); + // If a file search returns the same file twice, we would enter this branch. + // It's possible that could happen, #90813 + if (existingMatch.results && match.results) { + existingMatch.results.push(...match.results); + } } else { this.matches.set(match.resource.toString(), match); } 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 f78dc6ca963..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 { @@ -512,7 +513,7 @@ export class MainThreadTask implements MainThreadTaskShape { public $executeTask(value: TaskHandleDTO | TaskDTO): Promise { return new Promise((resolve, reject) => { if (TaskHandleDTO.is(value)) { - const workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); + const workspaceFolder = typeof value.workspaceFolder === 'string' ? value.workspaceFolder : this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); if (workspaceFolder) { this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task | undefined) => { if (!task) { @@ -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 919e4a18ff5..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 { ITimelineService, TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent } 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 { @@ -24,10 +24,6 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._proxy = context.getProxy(ExtHostContext.ExtHostTimeline); } - $getTimeline(uri: URI, token: CancellationToken): Promise { - return this._timelineService.getTimeline(uri, token); - } - $registerTimelineProvider(provider: TimelineProviderDescriptor): void { this.logService.trace(`MainThreadTimeline#registerTimelineProvider: id=${provider.id}`); @@ -43,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._timelineService.registerTimelineProvider({ ...provider, onDidChange: onDidChange.event, - provideTimeline(uri: URI, token: CancellationToken) { - return proxy.$getTimeline(provider.id, uri, token); + provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions) { + return proxy.$getTimeline(provider.id, uri, options, token, internalOptions); }, dispose() { emitters.delete(provider.id); 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 5726232e475..34b162bf55b 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 { CustomFileEditorInput } 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, IResolvedBackup } 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,20 +132,32 @@ 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, + @IBackupFileService private readonly _backupService: IBackupFileService, ) { 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. this._register(_webviewWorkbenchService.registerResolver({ canResolve: (webview: WebviewInput) => { - if (webview instanceof CustomFileEditorInput) { - extensionService.activateByEvent(`onWebviewEditor:${webview.viewType}`); + if (webview instanceof CustomEditorInput) { + extensionService.activateByEvent(`onCustomEditor:${webview.viewType}`); return false; } @@ -134,6 +169,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 +268,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 +289,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,47 +305,82 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._revivers.delete(viewType); } - public $registerEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): void { + 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 { + this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {}); + } + + private registerEditorProvider( + modelType: ModelType, + extensionData: extHostProtocol.WebviewExtensionDescription, + viewType: string, + options: modes.IWebviewPanelOptions, + 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 CustomFileEditorInput && webviewInput.viewType === viewType; + return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType; }, - resolveWebview: async (webviewInput: CustomFileEditorInput) => { + resolveWebview: async (webviewInput: CustomEditorInput, cancellation: CancellationToken) => { const handle = webviewInput.id; + const resource = webviewInput.resource; + this._webviewInputs.add(handle, webviewInput); this.hookupWebviewEventDelegate(handle, webviewInput); - webviewInput.webview.options = options; webviewInput.webview.extension = extension; - const resource = webviewInput.getResource(); - - const model = await this.retainCustomEditorModel(webviewInput, resource, viewType, capabilities); - webviewInput.onDisposeWebview(() => { - this.releaseCustomEditorModel(model); - }); + let modelRef: IReference; try { - await this._proxy.$resolveWebviewEditor( - resource, - handle, - viewType, - webviewInput.getTitle(), - editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), - webviewInput.webview.options - ); + modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType, cancellation); } catch (error) { onUnexpectedError(error); - webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); + 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, cancellation); + } catch (error) { + onUnexpectedError(error); + webviewInput.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType); + modelRef.dispose(); return; } } })); + + this._editorProviders.set(viewType, disposables); + + return disposables; } public $unregisterEditorProvider(viewType: string): void { @@ -311,80 +395,35 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._customEditorService.models.disposeAllModelsForView(viewType); } - private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]) { - const model = await this._customEditorService.models.resolve(webviewInput.getResource(), webviewInput.viewType); - - const existingEntry = this._customEditorModels.get(model); - if (existingEntry) { - ++existingEntry.referenceCount; - // no need to hook up listeners again - return model; + private async getOrCreateCustomEditorModel( + modelType: ModelType, + resource: URI, + viewType: string, + cancellation: CancellationToken, + ): Promise> { + const existingModel = this._customEditorService.models.tryRetain(resource, viewType); + if (existingModel) { + return existingModel; } - this._customEditorModels.set(model, { referenceCount: 1 }); + 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, this._backupService); - const capabilitiesSet = new Set(capabilities); - const isEditable = capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable); - if (isEditable) { - model.onUndo(e => { - this._proxy.$undoEdits(resource, viewType, e.edits); - }); - - model.onDisposeEdits(e => { - this._proxy.$disposeEdits(e.edits); - }); - - model.onApplyEdit(e => { - if (e.trigger !== model) { - this._proxy.$applyEdits(resource, viewType, e.edits); - } - }); - - model.onWillSave(e => { - e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType)); - }); - } - - // Save as should always be implemented even if the model is readonly - model.onWillSaveAs(e => { - if (isEditable) { - 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 */)); - } - }); - - if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.SupportsHotExit)) { - 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 entry = this._customEditorModels.get(model); - if (!entry) { - return; - } - - --entry.referenceCount; - if (entry.referenceCount <= 0) { - this._customEditorService.models.disposeModel(model); - this._customEditorModels.delete(model); - } - } - - public $onEdit(resource: UriComponents, viewType: string, editId: number): void { - 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.pushEdit(editId, model); + model.pushEdit(editId, label); } private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { @@ -394,22 +433,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) => { @@ -423,7 +481,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), }; } @@ -465,7 +523,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; } @@ -474,14 +532,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))} `; } } @@ -500,13 +558,314 @@ 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, + backupFileService: IBackupFileService, + ) { + const backup = await backupFileService.resolve(MainThreadCustomEditorModel.toWorkingCopyResource(viewType, resource)); + const { editable } = await proxy.$createCustomDocument(resource, viewType, backup?.meta?.backupId, cancellation); + return instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, backup, editable, getEditors); + } + + constructor( + private readonly _proxy: extHostProtocol.ExtHostWebviewsShape, + private readonly _viewType: string, + private readonly _editorResource: URI, + backup: IResolvedBackup | undefined, + 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, + ) { + super(); + + if (_editable) { + this._register(workingCopyService.registerWorkingCopy(this)); + } + this._fromBackup = !!backup; + } + + get editorResource() { + return this._editorResource; + } + + dispose() { + if (this._editable) { + this._undoService.removeElements(this._editorResource); + } + this._proxy.$disposeCustomDocument(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 MainThreadCustomEditorModel.toWorkingCopyResource(this._viewType, this._editorResource); + } + + private static toWorkingCopyResource(viewType: string, resource: URI) { + return URI.from({ + scheme: Schemas.vscodeCustomEditor, + authority: viewType, + path: resource.path, + query: JSON.stringify(resource.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.isDirty()); + } + + 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.isDirty()); + } + + 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; + } + + this._proxy.$revert(this._editorResource, this.viewType, CancellationToken.None); + 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, + backupId: '', + 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 { + const backupId = await pendingState.operation; + // Make sure state has not changed in the meantime + if (this._hotExitState === pendingState) { + this._hotExitState = HotExitState.Allowed; + backupData.meta!.backupId = backupId; + } + } 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 905d9774677..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 } } }; @@ -82,7 +87,7 @@ const viewDescriptor: IJSONSchema = { type: 'object', properties: { id: { - description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'), + description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. This should be unique across all views. It is recommended to include your extension id as part of the view id. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'), type: 'string' }, name: { @@ -100,7 +105,7 @@ const remoteViewDescriptor: IJSONSchema = { type: 'object', properties: { id: { - description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'), + description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. This should be unique across all views. It is recommended to include your extension id as part of the view id. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'), type: 'string' }, name: { @@ -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 { @@ -375,16 +384,15 @@ class ViewsExtensionHandler implements IWorkbenchContribution { collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key)); } const container = viewContainer || this.getDefaultViewContainer(); - const registeredViews = this.viewsRegistry.getViews(container); const viewIds: string[] = []; const viewDescriptors = coalesce(entry.value.map((item, index) => { // validate if (viewIds.indexOf(item.id) !== -1) { - collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}` in the view container `{1}`", item.id, container.id)); + collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}`", item.id)); return null; } - if (registeredViews.some(v => v.id === item.id)) { - collector.error(localize('duplicateView2', "A view with id `{0}` is already registered in the view container `{1}`", item.id, container.id)); + if (this.viewsRegistry.getView(item.id) !== null) { + collector.error(localize('duplicateView2', "A view with id `{0}` is already registered.", item.id)); return null; } @@ -397,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/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index 6e7bb05ba23..ea4e3428172 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -174,10 +174,20 @@ export class RemoveFromRecentlyOpenedAPICommand { } CommandsRegistry.registerCommand(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute)); +export interface OpenIssueReporterArgs { + readonly extensionId: string; + readonly issueTitle?: string; + readonly issueBody?: string; +} + export class OpenIssueReporter { public static readonly ID = 'vscode.openIssueReporter'; - public static execute(executor: ICommandsExecutor, extensionId: string): Promise { - return executor.executeCommand('workbench.action.openIssueReporter', [extensionId]); + + public static execute(executor: ICommandsExecutor, args: string | OpenIssueReporterArgs): Promise { + const commandArgs = typeof args === 'string' + ? { extensionId: args } + : args; + return executor.executeCommand('workbench.action.openIssueReporter', commandArgs); } } 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 61353fa2683..aa07e04925f 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'; @@ -113,7 +114,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); // manually create and register addressable instances - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); @@ -130,10 +130,12 @@ 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)); + const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService, extHostApiDeprecation, extHostDocuments)); // Check that no named customers are missing const expected: ProxyIdentifier[] = values(ExtHostContext); @@ -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,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, + registerTerminalLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable { + return extHostTerminalService.registerLinkHandler(handler); + }, registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, @@ -561,9 +581,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => { return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer); }, - registerWebviewCustomEditorProvider: (viewType: string, provider: vscode.WebviewCustomEditorProvider, 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.registerWebviewCustomEditorProvider(extension, viewType, provider, options); + return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options?.webviewOptions); }, registerDecorationProvider(provider: vscode.DecorationProvider) { checkProposedApiEnabled(extension); @@ -769,11 +792,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 +899,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 +925,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I comment, comments, tasks, + notebook, window, workspace, // types @@ -929,6 +961,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 +1033,11 @@ 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, }; }; } @@ -1015,6 +1049,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 +1059,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 73af521adef..44d8ca55244 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -49,7 +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 { TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent, TimelineItemWithSource } 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; @@ -64,6 +69,7 @@ export interface IEnvironment { userHome: URI; webviewResourceRoot: string; webviewCspSource: string; + useHostProxy?: boolean; } export interface IStaticWorkspaceData { @@ -151,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 { @@ -173,12 +179,14 @@ export interface MainThreadDialogOpenOptions { canSelectFolders?: boolean; canSelectMany?: boolean; filters?: { [name: string]: string[]; }; + title?: string; } export interface MainThreadDialogSaveOptions { defaultUri?: UriComponents; saveLabel?: string; filters?: { [name: string]: string[]; }; + title?: string; } export interface MainThreadDiaglogsShape extends IDisposable { @@ -355,14 +363,16 @@ 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): void; + $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $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; @@ -425,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; @@ -521,13 +534,13 @@ export interface MainThreadQuickOpenShape extends IDisposable { $show(instance: number, options: quickInput.IPickOptions, token: CancellationToken): Promise; $setItems(instance: number, items: TransferQuickPickItems[]): Promise; $setError(instance: number, error: Error): Promise; - $input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise; + $input(options: IInputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise; $createOrUpdate(params: TransferQuickInput): Promise; $dispose(id: number): Promise; } 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; } @@ -567,11 +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,10 +609,11 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; - $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly WebviewEditorCapabilities[]): 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; - $onEdit(resource: UriComponents, viewType: string, editId: number): void; + $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; } export interface WebviewPanelViewStateData { @@ -608,16 +631,69 @@ export interface ExtHostWebviewsShape { $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; $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; - $undoEdits(resource: UriComponents, viewType: string, editIds: readonly number[]): void; - $applyEdits(resource: UriComponents, viewType: string, editIds: readonly number[]): void; - $disposeEdits(editIds: readonly number[]): void; + $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions, cancellation: CancellationToken): Promise; + $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken): Promise<{ editable: boolean }>; + $disposeCustomDocument(resource: UriComponents, viewType: string): Promise; - $onSave(resource: UriComponents, viewType: string): Promise; - $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; + $undo(resource: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise; + $redo(resource: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise; + $revert(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; + $disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void; - $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; + $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 { @@ -797,14 +873,13 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $registerCandidateFinder(): Promise; $setTunnelProvider(): Promise; $setCandidateFilter(): Promise; + $tunnelServiceReady(): Promise; } export interface MainThreadTimelineShape extends IDisposable { $registerTimelineProvider(provider: TimelineProviderDescriptor): void; $unregisterTimelineProvider(source: string): void; - $emitTimelineChangeEvent(e: TimelineChangeEvent): void; - - $getTimeline(uri: UriComponents, token: CancellationToken): Promise; + $emitTimelineChangeEvent(e: TimelineChangeEvent | undefined): void; } // -- extension host @@ -1095,18 +1170,25 @@ export interface IWorkspaceSymbolsDto extends IdObject { symbols: IWorkspaceSymbolDto[]; } +export interface IWorkspaceEditEntryMetadataDto { + needsConfirmation: boolean; + label: string; + description?: string; + iconPath?: { id: string } | UriComponents | { light: UriComponents, dark: UriComponents }; +} + export interface IWorkspaceFileEditDto { oldUri?: UriComponents; newUri?: UriComponents; options?: modes.WorkspaceFileEditOptions - metadata?: modes.WorkspaceEditMetadata; + metadata?: IWorkspaceEditEntryMetadataDto; } export interface IWorkspaceTextEditDto { resource: UriComponents; edit: modes.TextEdit; modelVersionId?: number; - metadata?: modes.WorkspaceEditMetadata; + metadata?: IWorkspaceEditEntryMetadataDto; } export interface IWorkspaceEditDto { @@ -1125,6 +1207,9 @@ export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): mod (edit).newUri = URI.revive((edit).newUri); (edit).oldUri = URI.revive((edit).oldUri); } + if (edit.metadata && edit.metadata.iconPath) { + edit.metadata = revive(edit.metadata); + } } } return data; @@ -1178,16 +1263,7 @@ export interface ICodeLensDto { command?: ICommandDto; } -export interface ICallHierarchyItemDto { - _sessionId: string; - _itemId: string; - kind: modes.SymbolKind; - name: string; - detail?: string; - uri: UriComponents; - range: IRange; - selectionRange: IRange; -} +export type ICallHierarchyItemDto = Dto; export interface IIncomingCallDto { from: ICallHierarchyItemDto; @@ -1199,6 +1275,12 @@ export interface IOutgoingCallDto { to: ICallHierarchyItemDto; } +export interface ILanguageWordDefinitionDto { + languageId: string; + regexSource: string; + regexFlags: string +} + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1211,6 +1293,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; @@ -1241,6 +1324,7 @@ export interface ExtHostLanguageFeaturesShape { $provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseCallHierarchy(handle: number, sessionId: string): void; + $setWordDefinitions(wordDefinitions: ILanguageWordDefinitionDto[]): void; } export interface ExtHostQuickOpenShape { @@ -1297,12 +1381,14 @@ 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 { $provideOriginalResource(sourceControlHandle: number, uri: UriComponents, token: CancellationToken): Promise; $onInputBoxValueChange(sourceControlHandle: number, value: string): void; - $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): Promise; + $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise; $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined>; $setSelectedSourceControls(selectedSourceControlHandles: number[]): Promise; } @@ -1439,6 +1525,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; } @@ -1459,7 +1556,7 @@ export interface ExtHostTunnelServiceShape { } export interface ExtHostTimelineShape { - $getTimeline(source: string, uri: UriComponents, token: CancellationToken): Promise; + $getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise; } // --- proxy identifiers @@ -1504,6 +1601,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') @@ -1540,7 +1638,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 ab21443e9c4..4fcffb43157 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -14,7 +14,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures'; -import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter } from './apiCommands'; +import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter, OpenIssueReporterArgs } from './apiCommands'; import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IRange } from 'vs/editor/common/core/range'; @@ -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; @@ -364,7 +364,7 @@ export class ExtHostApiCommands { this._register(OpenIssueReporter.ID, adjustHandler(OpenIssueReporter.execute), { description: 'Opens the issue reporter with the provided extension id as the selected source', args: [ - { name: 'extensionId', description: 'extensionId to report an issue on', constraint: (value: any) => typeof value === 'string' } + { name: 'extensionId', description: 'extensionId to report an issue on', constraint: (value: unknown) => typeof value === 'string' || (typeof value === 'object' && typeof (value as OpenIssueReporterArgs).extensionId === 'string') } ] }); } diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index d9bcc811cbf..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, - accessToken: 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.accessToken(); - } - }; - }); - } - - 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): Promise { - 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); @@ -137,7 +157,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { const sessions = await authProvider.getSessions(); const session = sessions.find(session => session.id === sessionId); if (session) { - return session.accessToken(); + return session.getAccessToken(); } throw new Error(`Unable to find session with id: ${sessionId}`); 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 16f5e3bca7a..741a5b73800 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -26,7 +26,7 @@ import { Schemas } from 'vs/base/common/network'; import { VSBuffer } from 'vs/base/common/buffer'; import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { RemoteAuthorityResolverError } from 'vs/workbench/api/common/extHostTypes'; -import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; @@ -90,7 +90,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio private readonly _storage: ExtHostStorage; private readonly _storagePath: IExtensionStoragePaths; private readonly _activator: ExtensionsActivator; - private _extensionPathIndex: Promise> | null; + private _extensionPathIndex: Promise> | null; private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; @@ -194,7 +194,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio } catch (err) { // TODO: write to log once we have one } - await allPromises; + await Promise.all(allPromises); } public isActivated(extensionId: ExtensionIdentifier): boolean { @@ -236,7 +236,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio } // create trie to enable fast 'filename -> extension id' look up - public getExtensionPathIndex(): Promise> { + public getExtensionPathIndex(): Promise> { if (!this._extensionPathIndex) { const tree = TernarySearchTree.forPaths(); const extensions = this._registry.getAllExtensionDescriptions().map(ext => { @@ -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); }, @@ -641,7 +642,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const resolver = this._resolvers[authorityPrefix]; if (!resolver) { - throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`); + return { + type: 'error', + error: { + code: RemoteAuthorityResolverErrorCode.NoResolverFound, + message: `No remote extension installed to resolve ${authorityPrefix}.`, + detail: undefined + } + }; } try { @@ -784,6 +792,6 @@ export interface IExtHostExtensionService extends AbstractExtHostExtensionServic deactivateAll(): Promise; getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined; getExtensionRegistry(): Promise; - getExtensionPathIndex(): Promise>; + getExtensionPathIndex(): Promise>; registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable; } 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 65d12628b10..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 { @@ -1617,7 +1651,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF kind: x.kind.value, command: this._commands.converter.toInternal(x.command, store), })) - }); + }, ExtHostLanguageFeatures._extLabel(extension)); store.add(this._createDisposable(handle)); return store; } @@ -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 { @@ -1916,4 +1960,10 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._proxy.$setLanguageConfiguration(handle, languageId, serializedConfiguration); return this._createDisposable(handle); } + + $setWordDefinitions(wordDefinitions: extHostProtocol.ILanguageWordDefinitionDto[]): void { + for (const wordDefinition of wordDefinitions) { + this._documents.setWordDefinitionFor(wordDefinition.languageId, new RegExp(wordDefinition.regexSource, wordDefinition.regexFlags)); + } + } } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts new file mode 100644 index 00000000000..5e2ce8a9c3a --- /dev/null +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -0,0 +1,788 @@ +/*--------------------------------------------------------------------------------------------- + * 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 + }; + if (this._metadataChangeListener) { + this._metadataChangeListener.dispose(); + } + + const observableMetadata = getObservable(newMetadata); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this.updateMetadata(); + })); + + 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.CellDisplayOutput, 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/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts index c06e86c8305..97839338566 100644 --- a/src/vs/workbench/api/common/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -91,7 +91,7 @@ class VSCodeNodeModuleFactory implements INodeModuleFactory { constructor( private readonly _apiFactory: IExtensionApiFactory, - private readonly _extensionPaths: TernarySearchTree, + private readonly _extensionPaths: TernarySearchTree, private readonly _extensionRegistry: ExtensionDescriptionRegistry, private readonly _configProvider: ExtHostConfigProvider, private readonly _logService: ILogService, @@ -230,7 +230,7 @@ class OpenNodeModuleFactory implements INodeModuleFactory { private _mainThreadTelemetry: MainThreadTelemetryShape; constructor( - private readonly _extensionPaths: TernarySearchTree, + private readonly _extensionPaths: TernarySearchTree, private readonly _appUriScheme: string, @IExtHostRpcService rpcService: IExtHostRpcService, ) { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 34a3138ad32..fb3f7725d3a 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -266,14 +266,14 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG return this._resourceStatesMap.get(handle); } - $executeResourceCommand(handle: number): Promise { + $executeResourceCommand(handle: number, preserveFocus: boolean): Promise { const command = this._resourceStatesCommandsMap.get(handle); if (!command) { return Promise.resolve(undefined); } - return asPromise(() => this._commands.executeCommand(command.command, ...(command.arguments || []))); + return asPromise(() => this._commands.executeCommand(command.command, ...(command.arguments || []), preserveFocus)); } _takeResourceStateSnapshot(): SCMRawResourceSplice[] { @@ -628,7 +628,7 @@ export class ExtHostSCM implements ExtHostSCMShape { return Promise.resolve(undefined); } - $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): Promise { + $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise { this.logService.trace('ExtHostSCM#$executeResourceCommand', sourceControlHandle, groupHandle, handle); const sourceControl = this._sourceControls.get(sourceControlHandle); @@ -643,7 +643,7 @@ export class ExtHostSCM implements ExtHostSCMShape { return Promise.resolve(undefined); } - return group.$executeResourceCommand(handle); + return group.$executeResourceCommand(handle, preserveFocus); } $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined> { 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/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 8ae923128bc..71430153a3d 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -26,6 +26,7 @@ import { Schemas } from 'vs/base/common/network'; import * as Platform from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; export interface IExtHostTask extends ExtHostTaskShape { @@ -192,9 +193,11 @@ export namespace CustomExecutionDTO { export namespace TaskHandleDTO { export function from(value: types.Task): tasks.TaskHandleDTO { - let folder: UriComponents | undefined; + let folder: UriComponents | string; if (value.scope !== undefined && typeof value.scope !== 'number') { folder = value.scope.uri; + } else if (value.scope !== undefined && typeof value.scope === 'number') { + folder = USER_TASKS_GROUP_KEY; } return { id: value._id!, diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 9d81c5b30cb..a7443f8c730 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'); @@ -123,16 +128,16 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi strictEnv?: boolean, hideFromUser?: boolean ): Promise { - const terminal = await this._proxy.$createTerminal({ name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser }); - this._name = terminal.name; - this._runQueuedRequests(terminal.id); + const result = await this._proxy.$createTerminal({ name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser }); + this._name = result.name; + this._runQueuedRequests(result.id); } public async createExtensionTerminal(): Promise { - const terminal = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true }); - this._name = terminal.name; - this._runQueuedRequests(terminal.id); - return terminal.id; + const result = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true }); + this._name = result.name; + this._runQueuedRequests(result.id); + return result.id; } public get name(): string { @@ -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.call(thisArg, 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 1ce8f7813ff..c15a9e73617 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -7,16 +7,16 @@ 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 { TimelineItemWithSource, 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 } from 'vs/workbench/api/common/extHostCommands'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export interface IExtHostTimeline extends ExtHostTimelineShape { readonly _serviceBrand: undefined; - $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise; + $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: InternalTimelineOptions): Promise; } export const IExtHostTimeline = createDecorator('IExtHostTimeline'); @@ -28,82 +28,133 @@ export class ExtHostTimeline implements IExtHostTimeline { private _providers = new Map(); + private _itemsBySourceAndUriMap = new Map>>(); + constructor( mainContext: IMainContext, + commands: ExtHostCommands, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadTimeline); + + commands.registerArgumentProcessor({ + processArgument: arg => { + if (arg && arg.$mid === 11) { + const uri = arg.uri === undefined ? undefined : URI.revive(arg.uri); + return this._itemsBySourceAndUriMap.get(arg.source)?.get(getUriKey(uri))?.get(arg.handle); + } + + return arg; + } + }); } - async $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): 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), token) ?? []; + return provider?.provideTimeline(URI.revive(uri), options, token, internalOptions); } - registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { + registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, _extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { const timelineDisposables = new DisposableStore(); - const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables); + const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables).bind(this); let disposable: IDisposable | undefined; if (provider.onDidChange) { - disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.id), this); + disposable = provider.onDidChange(e => this._proxy.$emitTimelineChangeEvent({ uri: undefined, reset: true, ...e, id: provider.id }), this); } + const itemsBySourceAndUriMap = this._itemsBySourceAndUriMap; return this.registerTimelineProviderCore({ ...provider, scheme: scheme, onDidChange: undefined, - async provideTimeline(uri: URI, token: CancellationToken) { - timelineDisposables.clear(); + async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions) { + if (internalOptions?.resetCache) { + timelineDisposables.clear(); - const results = await provider.provideTimeline(uri, token); + // 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); // Intentional == we don't know how a provider will respond // eslint-disable-next-line eqeqeq - return results != null - ? results.map(item => convertTimelineItem(item)) - : []; + if (result == null) { + return undefined; + } + + // 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, + items: result.items.map(convertItem) + }; }, dispose() { + for (const sourceMap of itemsBySourceAndUriMap.values()) { + sourceMap.get(provider.id)?.clear(); + } + disposable?.dispose(); timelineDisposables.dispose(); } }); } - private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore): (item: vscode.TimelineItem) => TimelineItemWithSource { - return (item: vscode.TimelineItem) => { - const { iconPath, ...props } = item; + private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore) { + 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); + } - let icon; - let iconDark; - let themeIcon; - if (item.iconPath) { - if (iconPath instanceof ThemeIcon) { - themeIcon = { id: iconPath.id }; - } - else if (URI.isUri(iconPath)) { - icon = iconPath; - iconDark = iconPath; - } - else { - ({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI }); + const uriKey = getUriKey(uri); + items = itemsByUri.get(uriKey); + if (items === undefined) { + items = new Map(); + itemsByUri.set(uriKey, items); } } - return { - ...props, - source: source, - command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined, - icon: icon, - iconDark: iconDark, - themeIcon: themeIcon - }; - }; - } + return (item: vscode.TimelineItem): TimelineItem => { + const { iconPath, ...props } = item; - private emitTimelineChangeEvent(id: string) { - return (e: vscode.TimelineChangeEvent) => { - this._proxy.$emitTimelineChangeEvent({ ...e, id: id }); + const handle = `${source}|${item.id ?? item.timestamp}`; + items?.set(handle, item); + + let icon; + let iconDark; + let themeIcon; + if (item.iconPath) { + if (iconPath instanceof ThemeIcon) { + themeIcon = { id: iconPath.id }; + } + else if (URI.isUri(iconPath)) { + icon = iconPath; + iconDark = iconPath; + } + else { + ({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI }); + } + } + + return { + ...props, + id: props.id ?? undefined, + handle: handle, + source: source, + command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined, + icon: icon, + iconDark: iconDark, + themeIcon: themeIcon + }; + }; }; } @@ -123,9 +174,18 @@ export class ExtHostTimeline implements IExtHostTimeline { this._providers.set(provider.id, provider); return toDisposable(() => { + for (const sourceMap of this._itemsBySourceAndUriMap.values()) { + sourceMap.get(provider.id)?.clear(); + } + this._providers.delete(provider.id); this._proxy.$unregisterTimelineProvider(provider.id); provider.dispose(); }); } } + +function getUriKey(uri: URI | undefined): string | undefined { + return uri?.toString(); +} + 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 c998bd2c5f8..6ef467bd8a2 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -127,12 +127,17 @@ export namespace DiagnosticTag { export namespace Diagnostic { export function from(value: vscode.Diagnostic): IMarkerData { - let code: string | { value: string; target: URI } | undefined = isString(value.code) || isNumber(value.code) ? String(value.code) : undefined; - if (value.code2) { - code = { - value: String(value.code2.value), - target: value.code2.target - }; + let code: string | { value: string; target: URI } | undefined; + + if (value.code) { + if (isString(value.code) || isNumber(value.code)) { + code = String(value.code); + } else { + code = { + value: String(value.code.value), + target: value.code.target, + }; + } } return { @@ -257,7 +262,7 @@ export namespace MarkdownString { } else if (htmlContent.isMarkdownString(markup)) { res = markup; } else if (typeof markup === 'string') { - res = { value: markup }; + res = { value: markup }; } else { res = { value: '' }; } @@ -830,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; } @@ -861,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; } @@ -1088,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 f6871d7e2bd..d3a0c8654e3 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4,25 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce, equals } from 'vs/base/common/arrays'; +import { escapeCodicons } from 'vs/base/common/codicons'; import { illegalArgument } from 'vs/base/common/errors'; 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 { isStringArray } from 'vs/base/common/types'; 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'; 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 +42,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 +479,12 @@ export enum EndOfLine { CRLF = 2 } +export enum EnvironmentVariableMutatorType { + Replace = 1, + Append = 2, + Prepend = 3 +} + @es5ClassCompat export class TextEdit { @@ -576,14 +580,14 @@ export interface IFileOperation { from?: URI; to?: URI; options?: IFileOperationOptions; - metadata?: vscode.WorkspaceEditMetadata; + metadata?: vscode.WorkspaceEditEntryMetadata; } export interface IFileTextEdit { _type: 2; uri: URI; edit: TextEdit; - metadata?: vscode.WorkspaceEditMetadata; + metadata?: vscode.WorkspaceEditEntryMetadata; } @es5ClassCompat @@ -591,27 +595,27 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { private _edits = new Array(); - renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void { + renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void { this._edits.push({ _type: 1, from, to, options, metadata }); } - createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void { + createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void { this._edits.push({ _type: 1, from: undefined, to: uri, options, metadata }); } - deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void { + deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void { this._edits.push({ _type: 1, from: uri, to: undefined, options, metadata }); } - replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditMetadata): void { + replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void { this._edits.push({ _type: 2, uri, edit: new TextEdit(range, newText), metadata }); } - insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditMetadata): void { + insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void { this.replace(resource, new Range(position, position), newText, metadata); } - delete(resource: URI, range: Range, metadata?: vscode.WorkspaceEditMetadata): void { + delete(resource: URI, range: Range, metadata?: vscode.WorkspaceEditEntryMetadata): void { this.replace(resource, range, '', metadata); } @@ -661,7 +665,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { textEdit[1].push(candidate.edit); } } - return values(textEdits); + return [...textEdits.values()]; } allEntries(): ReadonlyArray { @@ -1349,7 +1353,9 @@ export enum CompletionItemKind { Struct = 21, Event = 22, Operator = 23, - TypeParameter = 24 + TypeParameter = 24, + User = 25, + Issue = 26 } export enum CompletionItemTag { @@ -1358,7 +1364,7 @@ export enum CompletionItemTag { export interface CompletionItemLabel { name: string; - signature?: string; + parameters?: string; qualifier?: string; type?: string; } @@ -2334,9 +2340,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 +2417,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 +2551,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 +2684,6 @@ export class Decoration { bubble?: boolean; } -export enum WebviewContentState { - Readonly = 1, - Unchanged = 2, - Dirty = 3, -} - - //#region Theming @es5ClassCompat @@ -2556,6 +2700,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 diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 338232d2a77..5e88b452eea 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -3,94 +3,113 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +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'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; +import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; 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 { Cache } from './cache'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; -import { Disposable as VSCodeDisposable } from './extHostTypes'; -import { CancellationToken } from 'vs/base/common/cancellation'; +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 _logService: ILogService, - ) { } + 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._initData.isExtensionDevelopmentDebug && !this._hasCalledAsWebviewUri) { - if (/(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { - this._hasCalledAsWebviewUri = true; - this._logService.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); - } + 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,29 +117,29 @@ 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; - private readonly _options: vscode.WebviewPanelOptions; - private readonly _webview: ExtHostWebview; - private _viewColumn: vscode.ViewColumn | undefined; - private _visible: boolean = true; - private _active: boolean = true; + readonly #webview: ExtHostWebview; + readonly #options: vscode.WebviewPanelOptions; - _isDisposed: boolean = false; + #title: string; + #iconPath?: IconPath; + #viewColumn: vscode.ViewColumn | undefined = undefined; + #visible: boolean = true; + #active: boolean = true; + #isDisposed: boolean = false; - readonly _onDisposeEmitter = this._register(new Emitter()); - public readonly onDidDispose: Event = this._onDisposeEmitter.event; + readonly #onDidDispose = this._register(new Emitter()); + public readonly onDidDispose = this.#onDidDispose.event; - readonly _onDidChangeViewStateEmitter = this._register(new Emitter()); - public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; + readonly #onDidChangeViewState = this._register(new Emitter()); + 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, @@ -128,151 +147,270 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa webview: ExtHostWebview ) { super(); - this._handle = handle; - this._proxy = proxy; - this._viewType = viewType; - this._options = editorOptions; - this._viewColumn = viewColumn; - this._title = title; - this._webview = webview; + this.#handle = handle; + this.#proxy = proxy; + this.#viewType = viewType; + this.#options = editorOptions; + this.#viewColumn = viewColumn; + this.#title = title; + this.#webview = webview; } public dispose() { - if (this._isDisposed) { + if (this.#isDisposed) { return; } - this._isDisposed = true; - this._onDisposeEmitter.fire(); - this._proxy.$disposeWebview(this._handle); - this._webview.dispose(); + + this.#isDisposed = true; + this.#onDidDispose.fire(); + this.#proxy.$disposeWebview(this.#handle); + this.#webview.dispose(); super.dispose(); } get webview() { this.assertNotDisposed(); - return this._webview; + return this.#webview; } 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); } } get options() { - return this._options; + return this.#options; } get viewColumn(): vscode.ViewColumn | undefined { this.assertNotDisposed(); - if (typeof this._viewColumn === 'number' && this._viewColumn < 0) { + if (typeof this.#viewColumn === 'number' && this.#viewColumn < 0) { // We are using a symbolic view column // Return undefined instead to indicate that the real view column is currently unknown but will be resolved. return undefined; } - return this._viewColumn; - } - - _setViewColumn(value: vscode.ViewColumn) { - this.assertNotDisposed(); - this._viewColumn = value; + return this.#viewColumn; } public get active(): boolean { this.assertNotDisposed(); - return this._active; - } - - _setActive(value: boolean) { - this.assertNotDisposed(); - this._active = value; + return this.#active; } public get visible(): boolean { this.assertNotDisposed(); - return this._visible; + return this.#visible; } - _setVisible(value: boolean) { - this.assertNotDisposed(); - this._visible = value; + _updateViewState(newState: { active: boolean; visible: boolean; viewColumn: vscode.ViewColumn; }) { + if (this.#isDisposed) { + return; + } + + if (this.active !== newState.active || this.visible !== newState.visible || this.viewColumn !== newState.viewColumn) { + this.#active = newState.active; + this.#visible = newState.visible; + this.#viewColumn = newState.viewColumn; + this.#onDidChangeViewState.fire({ webviewPanel: this }); + } } 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 }); } private assertNotDisposed() { - if (this._isDisposed) { + if (this.#isDisposed) { throw new Error('Webview is disposed'); } } } -export class ExtHostWebviews implements ExtHostWebviewsShape { +class CustomDocumentStoreEntry { - private static newHandle(): WebviewPanelHandle { + constructor( + public readonly document: vscode.CustomDocument, + ) { } + + private readonly _edits = new Cache('custom documents'); + + private _backup?: vscode.CustomDocumentBackup; + + + addEdit(item: vscode.CustomDocumentEditEvent): number { + return this._edits.add([item]); + } + + async undo(editId: number, isDirty: boolean): Promise { + await this.getEdit(editId).undo(); + if (!isDirty) { + this.disposeBackup(); + } + } + + async redo(editId: number, isDirty: boolean): Promise { + await this.getEdit(editId).redo(); + if (!isDirty) { + this.disposeBackup(); + } + } + + disposeEdits(editIds: number[]): void { + for (const id of editIds) { + this._edits.delete(id); + } + } + + updateBackup(backup: vscode.CustomDocumentBackup): void { + this._backup?.dispose(); + this._backup = backup; + } + + disposeBackup(): void { + this._backup?.dispose(); + this._backup = undefined; + } + + private getEdit(editId: number): vscode.CustomDocumentEditEvent { + const edit = this._edits.get(editId, 0); + if (!edit) { + throw new Error('No edit found'); + } + return edit; + } +} + +class CustomDocumentStore { + private readonly _documents = new Map(); + + public get(viewType: string, resource: vscode.Uri): CustomDocumentStoreEntry | undefined { + return this._documents.get(this.key(viewType, resource)); + } + + public add(viewType: string, document: vscode.CustomDocument): CustomDocumentStoreEntry { + const key = this.key(viewType, document.uri); + if (this._documents.has(key)) { + throw new Error(`Document already exists for viewType:${viewType} resource:${document.uri}`); + } + const entry = new CustomDocumentStoreEntry(document); + this._documents.set(key, entry); + return entry; + } + + public delete(viewType: string, document: vscode.CustomDocument) { + const key = this.key(viewType, document.uri); + this._documents.delete(key); + } + + private key(viewType: string, resource: vscode.Uri): string { + return `${viewType}@@@${resource}`; + } + +} + +const enum WebviewEditorType { + Text, + Custom +} + +type ProviderEntry = { + readonly extension: IExtensionDescription; + readonly type: WebviewEditorType.Text; + readonly provider: vscode.CustomTextEditorProvider; +} | { + readonly extension: IExtensionDescription; + readonly type: WebviewEditorType.Custom; + readonly provider: vscode.CustomEditorProvider; +}; + +class EditorProviderStore { + private readonly _providers = new Map(); + + public addTextProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider): vscode.Disposable { + return this.add(WebviewEditorType.Text, viewType, extension, provider); + } + + public addCustomProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomEditorProvider): vscode.Disposable { + return this.add(WebviewEditorType.Custom, viewType, extension, provider); + } + + public get(viewType: string): ProviderEntry | undefined { + return this._providers.get(viewType); + } + + private add(type: WebviewEditorType, viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider): vscode.Disposable { + if (this._providers.has(viewType)) { + throw new Error(`Provider for viewType:${viewType} already registered`); + } + this._providers.set(viewType, { type, extension, provider } as ProviderEntry); + return new extHostTypes.Disposable(() => this._providers.delete(viewType)); + } +} + +export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { + + 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(); - private readonly _editorProviders = new Map(); + private readonly _editorProviders = new EditorProviderStore(); - private readonly _edits = new Cache('edits'); + private readonly _documents = new CustomDocumentStore(); constructor( - mainContext: IMainContext, + mainContext: extHostProtocol.IMainContext, private readonly initData: WebviewInitData, private readonly workspace: IExtHostWorkspace | undefined, private readonly _logService: ILogService, + private readonly _deprecationService: IExtHostApiDeprecationService, + private readonly _extHostDocuments: ExtHostDocuments, ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); + this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadWebviews); } public createWebviewPanel( @@ -289,9 +427,9 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { }; const handle = ExtHostWebviews.newHandle(); - this._proxy.$createWebviewPanel({ id: extension.identifier, location: extension.extensionLocation }, handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options)); + this._proxy.$createWebviewPanel(toExtensionData(extension), handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options)); - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService); const panel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, viewColumn, options, webview); this._webviewPanels.set(handle, panel); return panel; @@ -309,39 +447,38 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._serializers.set(viewType, { serializer, extension }); this._proxy.$registerSerializer(viewType); - return new VSCodeDisposable(() => { + return new extHostTypes.Disposable(() => { this._serializers.delete(viewType); this._proxy.$unregisterSerializer(viewType); }); } - public registerWebviewCustomEditorProvider( + public registerCustomEditorProvider( extension: IExtensionDescription, viewType: string, - provider: vscode.WebviewCustomEditorProvider, - options?: vscode.WebviewPanelOptions, + provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider, + options: vscode.WebviewPanelOptions | undefined = {} ): vscode.Disposable { - if (this._editorProviders.has(viewType)) { - throw new Error(`Editor provider for '${viewType}' already registered`); + const disposables = new DisposableStore(); + if ('resolveCustomTextEditor' in provider) { + disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider)); + this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options, { + supportsMove: !!provider.moveCustomTextEditor, + }); + } else { + disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider)); + this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options); } - this._editorProviders.set(viewType, { extension, provider, }); - this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}, this.getCapabilites(provider)); - - // Hook up events - provider?.editingDelegate?.onEdit(({ edit, resource }) => { - const id = this._edits.add([edit]); - this._proxy.$onEdit(resource, viewType, id); - }); - - return new VSCodeDisposable(() => { - this._editorProviders.delete(viewType); - this._proxy.$unregisterEditorProvider(viewType); - }); + 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); @@ -351,13 +488,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 @@ -377,22 +514,20 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { for (const handle of handles) { const panel = this.getWebviewPanel(handle); - if (!panel || panel._isDisposed) { + if (!panel) { continue; } const newState = newStates[handle]; - const viewColumn = typeConverters.ViewColumn.to(newState.position); - if (panel.active !== newState.active || panel.visible !== newState.visible || panel.viewColumn !== viewColumn) { - panel._setActive(newState.active); - panel._setVisible(newState.visible); - panel._setViewColumn(viewColumn); - panel._onDidChangeViewStateEmitter.fire({ webviewPanel: panel }); - } + panel._updateViewState({ + active: newState.active, + visible: newState.visible, + viewColumn: typeConverters.ViewColumn.to(newState.position), + }); } } - async $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise { + async $onDidDisposeWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): Promise { const panel = this.getWebviewPanel(handle); if (panel) { panel.dispose(); @@ -401,7 +536,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } async $deserializeWebviewPanel( - webviewHandle: WebviewPanelHandle, + webviewHandle: extHostProtocol.WebviewPanelHandle, viewType: string, title: string, state: any, @@ -414,97 +549,179 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const { serializer, extension } = entry; - const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService); const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); await serializer.deserializeWebviewPanel(revivedPanel, state); } + async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) { + const entry = this._editorProviders.get(viewType); + if (!entry) { + throw new Error(`No provider found for '${viewType}'`); + } + + if (entry.type !== WebviewEditorType.Custom) { + throw new Error(`Invalid provide type for '${viewType}'`); + } + + const revivedResource = URI.revive(resource); + const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation); + const documentEntry = this._documents.add(viewType, document); + + if (this.isEditable(document)) { + document.onDidEdit(e => { + const editId = documentEntry.addEdit(e); + this._proxy.$onDidEdit(document.uri, viewType, editId, e.label); + }); + } + + return { editable: this.isEditable(document) }; + } + + async $disposeCustomDocument(resource: UriComponents, viewType: string): Promise { + const entry = this._editorProviders.get(viewType); + if (!entry) { + throw new Error(`No provider found for '${viewType}'`); + } + + if (entry.type !== WebviewEditorType.Custom) { + throw new Error(`Invalid provider type for '${viewType}'`); + } + + const revivedResource = URI.revive(resource); + const { document } = this.getCustomDocumentEntry(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) { - return Promise.reject(new Error(`No provider found for '${viewType}'`)); + throw new Error(`No provider found for '${viewType}'`); } - const { provider, extension } = entry; - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, entry.extension, this._deprecationService); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); + const revivedResource = URI.revive(resource); - await provider.resolveWebviewEditor(revivedResource, revivedPanel); - } - $undoEdits(resourceComponents: UriComponents, viewType: string, editIds: readonly number[]): void { - const provider = this.getEditorProvider(viewType); - if (!provider?.editingDelegate) { - return; - } - - const resource = URI.revive(resourceComponents); - const edits = editIds.map(id => this._edits.get(id, 0)); - provider.editingDelegate.undoEdits(resource, edits); - } - - $applyEdits(resourceComponents: UriComponents, viewType: string, editIds: readonly number[]): void { - const provider = this.getEditorProvider(viewType); - if (!provider?.editingDelegate) { - return; - } - - const resource = URI.revive(resourceComponents); - const edits = editIds.map(id => this._edits.get(id, 0)); - provider.editingDelegate.applyEdits(resource, edits); - } - - $disposeEdits(editIds: readonly number[]): void { - for (const edit of editIds) { - this._edits.delete(edit); + switch (entry.type) { + case WebviewEditorType.Custom: + { + const { document } = this.getCustomDocumentEntry(viewType, revivedResource); + return entry.provider.resolveCustomEditor(document, revivedPanel, cancellation); + } + case WebviewEditorType.Text: + { + const document = this._extHostDocuments.getDocument(revivedResource); + return entry.provider.resolveCustomTextEditor(document, revivedPanel, cancellation); + } + default: + { + throw new Error('Unknown webview provider type'); + } } } - async $onSave(resource: UriComponents, viewType: string): Promise { - const provider = this.getEditorProvider(viewType); - return provider?.editingDelegate?.save(URI.revive(resource)); + $disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void { + const document = this.getCustomDocumentEntry(viewType, resourceComponents); + document.disposeEdits(editIds); } - async $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise { - const provider = this.getEditorProvider(viewType); - return provider?.editingDelegate?.saveAs(URI.revive(resource), URI.revive(targetResource)); - } - - async $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise { - const provider = this.getEditorProvider(viewType); - if (!provider?.editingDelegate?.backup) { - return false; + 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}'`); } - return provider.editingDelegate.backup(URI.revive(resource), cancellation); + + 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); } - private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { + async $undo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise { + const entry = this.getCustomDocumentEntry(viewType, resourceComponents); + return entry.undo(editId, isDirty); + } + + async $redo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise { + const entry = this.getCustomDocumentEntry(viewType, resourceComponents); + return entry.redo(editId, isDirty); + } + + async $revert(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { + const entry = this.getCustomDocumentEntry(viewType, resourceComponents); + const document = this.getEditableCustomDocument(viewType, resourceComponents); + await document.revert(cancellation); + entry.disposeBackup(); + } + + async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { + const entry = this.getCustomDocumentEntry(viewType, resourceComponents); + const document = this.getEditableCustomDocument(viewType, resourceComponents); + await document.save(cancellation); + entry.disposeBackup(); + } + + async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise { + const document = this.getEditableCustomDocument(viewType, resourceComponents); + return document.saveAs(URI.revive(targetResource), cancellation); + } + + async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { + const entry = this.getCustomDocumentEntry(viewType, resourceComponents); + const document = this.getEditableCustomDocument(viewType, resourceComponents); + const backup = await document.backup(cancellation); + entry.updateBackup(backup); + return backup.backupId; + } + + private getWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebviewEditor | undefined { return this._webviewPanels.get(handle); } - private getEditorProvider(viewType: string): vscode.WebviewCustomEditorProvider | undefined { - return this._editorProviders.get(viewType)?.provider; + private getCustomDocumentEntry(viewType: string, resource: UriComponents): CustomDocumentStoreEntry { + const entry = this._documents.get(viewType, URI.revive(resource)); + if (!entry) { + throw new Error('No custom document found'); + } + return entry; } - private getCapabilites(capabilities: vscode.WebviewCustomEditorProvider) { - const declaredCapabilites: WebviewEditorCapabilities[] = []; - if (capabilities.editingDelegate) { - declaredCapabilites.push(WebviewEditorCapabilities.Editable); - } - if (capabilities.editingDelegate?.backup) { - declaredCapabilites.push(WebviewEditorCapabilities.SupportsHotExit); - } - return declaredCapabilites; + private isEditable(document: vscode.CustomDocument): document is vscode.EditableCustomDocument { + return !!(document as vscode.EditableCustomDocument).onDidEdit; } + + private getEditableCustomDocument(viewType: string, resource: UriComponents): vscode.EditableCustomDocument { + const { document } = this.getCustomDocumentEntry(viewType, resource); + if (!this.isEditable(document)) { + throw new Error('Custom document is not editable'); + } + return document; + } +} + +function toExtensionData(extension: IExtensionDescription): extHostProtocol.WebviewExtensionDescription { + return { id: extension.identifier, location: extension.extensionLocation }; } function convertWebviewOptions( diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 4b5c0c1301f..760f5a2454f 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -116,7 +116,7 @@ class ExtHostWorkspaceImpl extends Workspace { } private readonly _workspaceFolders: vscode.WorkspaceFolder[] = []; - private readonly _structure = TernarySearchTree.forPaths(); + private readonly _structure = TernarySearchTree.forUris(); constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[], configuration: URI | null, private _isUntitled: boolean) { super(id, folders.map(f => new WorkspaceFolder(f)), configuration); @@ -124,7 +124,7 @@ class ExtHostWorkspaceImpl extends Workspace { // setup the workspace folder data structure folders.forEach(folder => { this._workspaceFolders.push(folder); - this._structure.set(folder.uri.toString(), folder); + this._structure.set(folder.uri, folder); }); } @@ -141,15 +141,15 @@ class ExtHostWorkspaceImpl extends Workspace { } getWorkspaceFolder(uri: URI, resolveParent?: boolean): vscode.WorkspaceFolder | undefined { - if (resolveParent && this._structure.get(uri.toString())) { + if (resolveParent && this._structure.get(uri)) { // `uri` is a workspace folder so we check for its parent uri = dirname(uri); } - return this._structure.findSubstr(uri.toString()); + return this._structure.findSubstr(uri); } resolveWorkspaceFolder(uri: URI): vscode.WorkspaceFolder | undefined { - return this._structure.get(uri.toString()); + return this._structure.get(uri); } } @@ -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 66b60ca3743..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; } @@ -69,8 +73,8 @@ export class JSONValidationExtensionPoint { } catch (e) { collector.error(nls.localize('invalid.url.fileschema', "'configuration.jsonValidation.url' is an invalid relative URL: {0}", e.message)); } - } else if (!strings.startsWith(uri, 'https:/') && strings.startsWith(uri, 'https:/')) { - collector.error(nls.localize('invalid.url.schema', "'configuration.jsonValidation.url' must start with 'http:', 'https:' or './' to reference schemas located in the extension")); + } else if (!/^[^:/?#]+:\/\//.test(uri)) { + collector.error(nls.localize('invalid.url.schema', "'configuration.jsonValidation.url' must be an absolute URL or start with './' to reference schemas located in the extension.")); return; } }); diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index c79a3c16ab0..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,7 +52,10 @@ 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; } return undefined; @@ -61,6 +65,7 @@ namespace schema { switch (menuId) { case MenuId.StatusBarWindowIndicatorMenu: case MenuId.MenubarFileMenu: + case MenuId.MenubarWebNavigationMenu: return true; } return false; @@ -160,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', @@ -180,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', @@ -210,11 +230,26 @@ 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', items: menuItem }, + 'timeline/title': { + description: localize('view.timelineTitle', "The Timeline view title menu"), + type: 'array', + items: menuItem + }, + 'timeline/item/context': { + description: localize('view.timelineContext', "The Timeline view item context menu"), + type: 'array', + items: menuItem + }, } }; 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..8c6795c97a9 --- /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, uint8Arr.length / 4); + } 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, data.length / 4); + } +} + +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/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts index 465a042d8b5..17396177e5b 100644 --- a/src/vs/workbench/api/common/shared/tasks.ts +++ b/src/vs/workbench/api/common/shared/tasks.ts @@ -78,7 +78,7 @@ export interface TaskSourceDTO { export interface TaskHandleDTO { id: string; - workspaceFolder: UriComponents; + workspaceFolder: UriComponents | string; } export interface TaskDTO { 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 ca790979d28..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; @@ -46,15 +53,15 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, { name, shellPath, shellArgs }, name); - terminal.create(shellPath, shellArgs); this._terminals.push(terminal); + terminal.create(shellPath, shellArgs); return terminal; } public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options, options.name); - terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv, options.hideFromUser); this._terminals.push(terminal); + terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv, options.hideFromUser); return terminal; } @@ -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 42dc7547dd5..5ad55ee2b3a 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -3,13 +3,11 @@ * 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'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -18,14 +16,19 @@ 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 } from 'vs/platform/contextkey/common/contextkey'; +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, 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 { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -const registry = Registry.as(Extensions.WorkbenchActions); +const registry = Registry.as(WorkbenchExtensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); // --- Close Side Bar @@ -45,14 +48,12 @@ export class CloseSidebarAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { this.layoutService.setSideBarHidden(true); - - return Promise.resolve(); } } -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 @@ -74,7 +75,7 @@ export class ToggleActivityBarVisibilityAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + run(): Promise { const visibility = this.layoutService.isVisible(Parts.ACTIVITYBAR_PART); const newVisibilityValue = !visibility; @@ -110,10 +111,8 @@ class ToggleCenteredLayout extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { this.layoutService.centerEditorLayout(!this.layoutService.isEditorLayoutCentered()); - - return Promise.resolve(); } } @@ -160,11 +159,9 @@ export class ToggleEditorLayoutAction extends Action { this.enabled = this.editorGroupService.count > 1; } - run(): Promise { + async run(): Promise { const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL; this.editorGroupService.setGroupOrientation(newOrientation); - - return Promise.resolve(); } } @@ -200,7 +197,7 @@ export class ToggleSidebarPositionAction extends Action { this.enabled = !!this.layoutService && !!this.configurationService; } - run(): Promise { + run(): Promise { const position = this.layoutService.getSideBarPosition(); const newPositionValue = (position === Position.LEFT) ? 'right' : 'left'; @@ -238,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, @@ -250,10 +247,8 @@ export class ToggleEditorVisibilityAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { this.layoutService.toggleMaximizedPanel(); - - return Promise.resolve(); } } @@ -284,11 +279,9 @@ export class ToggleSidebarVisibilityAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { const hideSidebar = this.layoutService.isVisible(Parts.SIDEBAR_PART); this.layoutService.setSideBarHidden(hideSidebar); - - return Promise.resolve(); } } @@ -331,7 +324,7 @@ export class ToggleStatusbarVisibilityAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + run(): Promise { const visibility = this.layoutService.isVisible(Parts.STATUSBAR_PART); const newVisibilityValue = !visibility; @@ -368,7 +361,7 @@ class ToggleTabsVisibilityAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { const visibility = this.configurationService.getValue(ToggleTabsVisibilityAction.tabsVisibleKey); const newVisibilityValue = !visibility; @@ -398,10 +391,8 @@ class ToggleZenMode extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { this.layoutService.toggleZenMode(); - - return Promise.resolve(); } } @@ -461,9 +452,7 @@ export class ToggleMenuBarAction extends Action { newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default'; } - this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER); - - return Promise.resolve(); + return this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER); } } @@ -482,6 +471,240 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 0 }); +// --- Reset View Positions + +export class ResetViewLocationsAction extends Action { + static readonly ID = 'workbench.action.resetViewLocations'; + static readonly LABEL = nls.localize('resetViewLocations', "Reset View Locations"); + + constructor( + id: string, + label: string, + @IViewDescriptorService private viewDescriptorService: IViewDescriptorService + ) { + super(id, label); + } + + async run(): Promise { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + viewContainerRegistry.all.forEach(viewContainer => { + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); + + viewDescriptors.allViewDescriptors.forEach(viewDescriptor => { + const defaultContainer = this.viewDescriptorService.getDefaultContainer(viewDescriptor.id); + const currentContainer = this.viewDescriptorService.getViewContainer(viewDescriptor.id); + + if (defaultContainer && currentContainer !== defaultContainer) { + this.viewDescriptorService.moveViewsToContainer([viewDescriptor], defaultContainer); + } + }); + }); + } +} + +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'; + static readonly LABEL = nls.localize('moveFocusedView', "Move Focused View"); + + constructor( + id: string, + label: string, + @IViewDescriptorService private viewDescriptorService: IViewDescriptorService, + @IViewsService private viewsService: IViewsService, + @IQuickInputService private quickInputService: IQuickInputService, + @IContextKeyService private contextKeyService: IContextKeyService, + @INotificationService private notificationService: INotificationService, + @IActivityBarService private activityBarService: IActivityBarService, + @IPanelService private panelService: IPanelService + ) { + super(id, label); + } + + async run(): Promise { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + + const focusedViewId = FocusedViewContext.getValue(this.contextKeyService); + + if (focusedViewId === undefined || focusedViewId.trim() === '') { + this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused.")); + return; + } + + const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedViewId); + if (!viewDescriptor || !viewDescriptor.canMoveView) { + this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable.")); + return; + } + + 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(); + items.push(...pinnedViewlets + .filter(viewletId => { + if (viewletId === this.viewDescriptorService.getViewContainer(focusedViewId)!.id) { + return false; + } + + return !viewContainerRegistry.get(viewletId)!.rejectAddedViews; + }) + .map(viewletId => { + return { + id: viewletId, + label: viewContainerRegistry.get(viewletId)!.name + }; + })); + + 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(() => { + const destination = quickPick.selectedItems[0]; + + 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); + } + + quickPick.hide(); + }); + + quickPick.show(); + } +} + +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 { @@ -529,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); } } @@ -549,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 b4eb01994cf..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' }); - } - } } }); @@ -736,6 +606,35 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.toggleSelection', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchListFocusContextKey, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter, + handler: (accessor) => { + const widget = accessor.get(IListService).lastFocusedList; + + if (!widget) { + return; + } + + const focus = widget.getFocus(); + + if (focus.length === 0) { + return; + } + + const selection = widget.getSelection(); + const index = selection.indexOf(focus[0]); + + if (index > -1) { + widget.setSelection([...selection.slice(0, index), ...selection.slice(index + 1)]); + } else { + widget.setSelection([...selection, focus[0]]); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.toggleExpand', weight: KeybindingWeight.WorkbenchContrib, @@ -755,13 +654,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } tree.toggleCollapsed(focus[0]); - } else { - const tree = focused; - const focus = tree.getFocus(); - - if (focus) { - tree.toggleExpansion(focus); - } } } } @@ -783,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'); @@ -791,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' }); - } } }); @@ -813,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(); @@ -831,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 }); @@ -847,7 +731,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } @@ -863,7 +747,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const focused = accessor.get(IListService).lastFocusedList; - if (!focused || isLegacyTree(focused)) { + if (!focused) { return; } @@ -872,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; } @@ -893,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..8bef21b5d17 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. *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .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/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index a3669a60af3..e9dc403f254 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -30,7 +30,7 @@ abstract class BaseNavigationAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const isEditorFocus = this.layoutService.hasFocus(Parts.EDITOR_PART); const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART); const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART); @@ -39,7 +39,7 @@ abstract class BaseNavigationAction extends Action { if (isEditorFocus) { const didNavigate = this.navigateAcrossEditorGroup(this.toGroupDirection(this.direction)); if (didNavigate) { - return Promise.resolve(true); + return true; } neighborPart = this.layoutService.getVisibleNeighborPart(Parts.EDITOR_PART, this.direction); @@ -54,7 +54,7 @@ abstract class BaseNavigationAction extends Action { } if (neighborPart === Parts.EDITOR_PART) { - return Promise.resolve(this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST)); + return this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST); } if (neighborPart === Parts.SIDEBAR_PART) { @@ -65,7 +65,7 @@ abstract class BaseNavigationAction extends Action { return this.navigateToPanel(); } - return Promise.resolve(false); + return false; } private async navigateToPanel(): Promise { @@ -90,12 +90,12 @@ abstract class BaseNavigationAction extends Action { private async navigateToSidebar(): Promise { if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { - return Promise.resolve(false); + return false; } const activeViewlet = this.viewletService.getActiveViewlet(); if (!activeViewlet) { - return Promise.resolve(false); + return false; } const activeViewletId = activeViewlet.getId(); 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 c8941a826a1..1b2d9636354 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -37,7 +37,7 @@ export class OpenFileAction extends Action { super(id, label); } - run(event?: any, data?: ITelemetryData): Promise { + run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -55,7 +55,7 @@ export class OpenFolderAction extends Action { super(id, label); } - run(event?: any, data?: ITelemetryData): Promise { + run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -73,7 +73,7 @@ export class OpenFileFolderAction extends Action { super(id, label); } - run(event?: any, data?: ITelemetryData): Promise { + run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -91,7 +91,7 @@ export class OpenWorkspaceAction extends Action { super(id, label); } - run(event?: any, data?: ITelemetryData): Promise { + run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data }); } } @@ -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 }); @@ -139,12 +138,11 @@ export class OpenWorkspaceConfigFileAction extends Action { this.enabled = !!this.workspaceContextService.getWorkspace().configuration; } - run(): Promise { + async run(): Promise { const configuration = this.workspaceContextService.getWorkspace().configuration; if (configuration) { - return this.editorService.openEditor({ resource: configuration }); + await this.editorService.openEditor({ resource: configuration }); } - return Promise.resolve(); } } @@ -161,7 +159,7 @@ export class AddRootFolderAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID); } } @@ -181,7 +179,7 @@ export class GlobalRemoveRootFolderAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const state = this.contextService.getWorkbenchState(); // Workspace / Folder @@ -191,8 +189,6 @@ export class GlobalRemoveRootFolderAction extends Action { await this.workspaceEditingService.removeFolders([folder.uri]); } } - - return true; } } @@ -211,7 +207,7 @@ export class SaveWorkspaceAsAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); if (configPathUri && hasWorkspaceFileExtension(configPathUri)) { switch (this.contextService.getWorkbenchState()) { @@ -243,7 +239,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const folders = this.workspaceContextService.getWorkspace().folders; const 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 5b9eb95f871..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,16 +20,16 @@ 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'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { isStandalone } from 'vs/base/browser/browser'; -import { IModelService } from 'vs/editor/common/services/modelService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { Emitter } from 'vs/base/common/event'; export interface IDraggedResource { resource: URI; @@ -197,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, @@ -240,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 }).getResource(); + 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; } @@ -299,7 +299,7 @@ export class ResourcesDropHandler { // Open in separate windows if we drop workspaces or just one folder if (toOpen.length > folderURIs.length || folderURIs.length === 1) { - await this.hostService.openWindow(toOpen, { forceReuseWindow: true }); + await this.hostService.openWindow(toOpen); } // folders.length > 1: Multiple folders: Create new workspace with folders and open @@ -343,19 +343,18 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: // Editors: enables cross window DND of tabs into the editor area const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); - const modelService = accessor.get(IModelService); const draggedEditors: ISerializedDraggedEditor[] = []; files.forEach(file => { // 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; } } @@ -374,11 +373,8 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: // If the resource is dirty or untitled, send over its content // to restore dirty state. Get that from the text model directly let content: string | undefined = undefined; - if (textFileService.isDirty(file.resource)) { - const textModel = modelService.getModel(file.resource); - if (textModel) { - content = textModel.getValue(); - } + if (model?.isDirty()) { + content = model.textEditorModel.getValue(); } // Add as dragged editor @@ -512,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 5c87849e9ea..426e1929d30 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -4,18 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as resources from 'vs/base/common/resources'; +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 { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IDecorationsService, IResourceDecorationChangeEvent } from 'vs/workbench/services/decorations/browser/decorations'; import { Schemas } from 'vs/base/common/network'; -import { FileKind, FILES_ASSOCIATIONS_CONFIG, IFileService } from 'vs/platform/files/common/files'; +import { FileKind, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { ITextModel } from 'vs/editor/common/model'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; @@ -26,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 }; @@ -83,12 +93,11 @@ 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, - @IFileService private readonly fileService: IFileService, @ILabelService private readonly labelService: ILabelService, @ITextFileService private readonly textFileService: ITextFileService ) { @@ -105,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 => { @@ -113,10 +122,6 @@ export class ResourceLabels extends Disposable { return; // we need the resource to compare } - if (this.fileService.canHandleResource(e.model.uri) && e.oldModeId === PLAINTEXT_MODE_ID) { - return; // ignore transitions in files from no mode to specific mode because this happens each time a model is created - } - this._widgets.forEach(widget => widget.notifyModelModeChanged(e.model)); })); @@ -133,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 => { @@ -143,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(resource => { - this._widgets.forEach(widget => widget.notifyUntitledLabelChange(resource)); - }); + this._register(this.textFileService.untitled.onDidChangeLabel(model => { + this._widgets.forEach(widget => widget.notifyUntitledLabelChange(model.resource)); + })); } get(index: number): IResourceLabel { @@ -212,16 +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, - @IFileService fileService: IFileService, @ILabelService labelService: ILabelService, @ITextFileService textFileService: ITextFileService ) { - super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, fileService, labelService, textFileService); + super(DEFAULT_LABELS_CONTAINER, instantiationService, configurationService, modelService, modeService, decorationsService, themeService, labelService, textFileService); this._label = this._register(this.create(container, options)); } @@ -239,7 +243,7 @@ enum Redraw { class ResourceLabelWidget extends IconLabel { private _onDidRender = this._register(new Emitter()); - readonly onDidRender: Event = this._onDidRender.event; + readonly onDidRender = this._onDidRender.event; private readonly renderDisposables = this._register(new DisposableStore()); @@ -297,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); } } @@ -318,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 (resources.isEqual(resource, this.label?.resource)) { + if (isEqual(resource, toResource(this.label))) { this.render(false); } } @@ -340,20 +351,23 @@ class ResourceLabelWidget extends IconLabel { } if (!name) { - name = resources.basenameOrAuthority(resource); + name = basenameOrAuthority(resource); } } let description: string | undefined; if (!options?.hidePath) { - description = this.labelService.getUriLabel(resources.dirname(resource), { relative: true }); + description = this.labelService.getUriLabel(dirname(resource), { relative: true }); } this.setResource({ resource, name, description }, options); } - setResource(label: IResourceLabelProps, options?: IResourceLabelOptions): void { - if (label.resource?.scheme === Schemas.untitled) { + setResource(label: IResourceLabelProps, options: IResourceLabelOptions = Object.create(null)): void { + 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 @@ -361,24 +375,31 @@ 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; } if (typeof label.description === 'string') { - let untitledDescription: string; - if (untitledModel.hasAssociatedFilePath) { - untitledDescription = this.labelService.getUriLabel(resources.dirname(untitledModel.resource), { relative: true }); - } else { - untitledDescription = untitledModel.resource.path; - } - + let untitledDescription = untitledModel.resource.path; if (label.name !== untitledDescription) { label.description = untitledDescription; + } else { + label.description = undefined; } } + + let untitledTitle = untitledModel.resource.path; + if (untitledModel.name !== untitledTitle) { + options.title = `${untitledModel.name} • ${untitledTitle}`; + } else { + options.title = untitledTitle; + } } } @@ -418,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); } @@ -447,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; @@ -466,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 f94fe18534f..3d0ea52bf9e 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size } from 'vs/base/browser/dom'; +import { Emitter } from 'vs/base/common/event'; +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'; import { isWindows, isLinux, isMacintosh, isWeb, isNative } from 'vs/base/common/platform'; -import { pathsToEditors } from 'vs/workbench/common/editor'; +import { pathsToEditors, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; @@ -25,11 +25,11 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; 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'; @@ -40,6 +40,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; +import { LineNumbersType } from 'vs/editor/common/config/editorOptions'; enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', @@ -87,26 +88,26 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi //#region Events - private readonly _onZenModeChange: Emitter = this._register(new Emitter()); - readonly onZenModeChange: Event = this._onZenModeChange.event; + private readonly _onZenModeChange = this._register(new Emitter()); + readonly onZenModeChange = this._onZenModeChange.event; - private readonly _onFullscreenChange: Emitter = this._register(new Emitter()); - readonly onFullscreenChange: Event = this._onFullscreenChange.event; + private readonly _onFullscreenChange = this._register(new Emitter()); + readonly onFullscreenChange = this._onFullscreenChange.event; - private readonly _onCenteredLayoutChange: Emitter = this._register(new Emitter()); - readonly onCenteredLayoutChange: Event = this._onCenteredLayoutChange.event; + private readonly _onCenteredLayoutChange = this._register(new Emitter()); + readonly onCenteredLayoutChange = this._onCenteredLayoutChange.event; - private readonly _onMaximizeChange: Emitter = this._register(new Emitter()); - readonly onMaximizeChange: Event = this._onMaximizeChange.event; + private readonly _onMaximizeChange = this._register(new Emitter()); + readonly onMaximizeChange = this._onMaximizeChange.event; - private readonly _onPanelPositionChange: Emitter = this._register(new Emitter()); - readonly onPanelPositionChange: Event = this._onPanelPositionChange.event; + private readonly _onPanelPositionChange = this._register(new Emitter()); + readonly onPanelPositionChange = this._onPanelPositionChange.event; - private readonly _onPartVisibilityChange: Emitter = this._register(new Emitter()); - readonly onPartVisibilityChange: Event = this._onPartVisibilityChange.event; + private readonly _onPartVisibilityChange = this._register(new Emitter()); + readonly onPartVisibilityChange = this._onPartVisibilityChange.event; private readonly _onLayout = this._register(new Emitter()); - readonly onLayout: Event = this._onLayout.event; + readonly onLayout = this._onLayout.event; //#endregion @@ -116,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; @@ -171,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: { @@ -194,7 +208,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi wasSideBarVisible: false, wasPanelVisible: false, transitionDisposables: new DisposableStore(), - setNotificationsFilter: false + setNotificationsFilter: false, + editorWidgetSet: new Set() }, }; @@ -252,6 +267,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(this.editorService.onDidVisibleEditorsChange(showEditorIfHidden)); this._register(this.editorGroupService.onDidActivateGroup(showEditorIfHidden)); + // Revalidate center layout when active editor changes: diff editor quits centered mode. + this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.state.editor.centered))); + // Configuration changes this._register(this.configurationService.onDidChangeConfiguration(() => this.doUpdateLayoutConfiguration())); @@ -271,7 +289,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))); @@ -400,7 +418,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); @@ -409,11 +427,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) { @@ -511,7 +527,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(); @@ -647,17 +663,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 { @@ -682,26 +693,43 @@ 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(); - const setLineNumbers = (lineNumbers?: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => { - // To properly reset line numbers we need to read the configuration for each editor respecting it's uri. - if (!lineNumbers && isCodeEditor(editor) && editor.hasModel()) { - const model = editor.getModel(); - lineNumbers = this.configurationService.getValue('editor.lineNumbers', { resource: model.uri, overrideIdentifier: model.getModeId() }); - } - if (!lineNumbers) { - lineNumbers = this.configurationService.getValue('editor.lineNumbers'); - } + const setLineNumbers = (lineNumbers?: LineNumbersType) => { + const setEditorLineNumbers = (editor: IEditor) => { + // To properly reset line numbers we need to read the configuration for each editor respecting it's uri. + if (!lineNumbers && isCodeEditor(editor) && editor.hasModel()) { + const model = editor.getModel(); + lineNumbers = this.configurationService.getValue('editor.lineNumbers', { resource: model.uri, overrideIdentifier: model.getModeId() }); + } + if (!lineNumbers) { + lineNumbers = this.configurationService.getValue('editor.lineNumbers'); + } - editor.updateOptions({ lineNumbers }); - }); + editor.updateOptions({ lineNumbers }); + }; + + const editorControlSet = this.state.zenMode.editorWidgetSet; + if (!lineNumbers) { + // Reset line numbers on all editors visible and non-visible + for (const editor of editorControlSet) { + setEditorLineNumbers(editor); + } + editorControlSet.clear(); + } else { + this.editorService.visibleTextEditorControls.forEach(editorControl => { + if (!editorControlSet.has(editorControl)) { + editorControlSet.add(editorControl); + this.state.zenMode.transitionDisposables.add(editorControl.onDidDispose(() => { + editorControlSet.delete(editorControl); + })); + } + setEditorLineNumbers(editorControl); + }); + } + }; // Check if zen mode transitioned to full screen and if now we are out of zen mode // -> we need to go out of full screen (same goes for the centered editor layout) @@ -782,7 +810,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); } @@ -931,7 +959,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.storageService.store(Storage.CENTERED_LAYOUT_ENABLED, active, StorageScope.WORKSPACE); let smartActive = active; - if (this.editorGroupService.groups.length > 1 && this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize')) { + const activeEditor = this.editorService.activeEditor; + if (this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize') + && (this.editorGroupService.groups.length > 1 || (activeEditor && activeEditor instanceof SideBySideEditorInput))) { smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group. } @@ -1066,7 +1096,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(); } } @@ -1156,6 +1186,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } else { this.setEditorHidden(false); this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.lastNonMaximizedWidth, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.lastNonMaximizedHeight : size.height }); + this.editorGroupService.activeGroup.focus(); } } diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 89dd7fe4c9b..b49e0709d8e 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -8,6 +8,22 @@ 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/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 0409e371f7b..341355e0efd 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -18,7 +18,9 @@ import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IAction, IActionViewItem } from 'vs/base/common/actions'; export class PaneComposite extends Composite implements IPaneComposite { - constructor(id: string, + + constructor( + id: string, protected readonly viewPaneContainer: ViewPaneContainer, @ITelemetryService telemetryService: ITelemetryService, @@ -33,24 +35,30 @@ export class PaneComposite extends Composite implements IPaneComposite { @IExtensionService protected extensionService: IExtensionService, @IWorkspaceContextService - protected contextService: IWorkspaceContextService) { + protected contextService: IWorkspaceContextService + ) { super(id, telemetryService, themeService, storageService); this._register(this.viewPaneContainer.onTitleAreaUpdate(() => this.updateTitleArea())); } + create(parent: HTMLElement): void { this.viewPaneContainer.create(parent); } + setVisible(visible: boolean): void { super.setVisible(visible); this.viewPaneContainer.setVisible(visible); } + layout(dimension: Dimension): void { this.viewPaneContainer.layout(dimension); } + getOptimalWidth(): number { return this.viewPaneContainer.getOptimalWidth(); } + openView(id: string, focus?: boolean): IView { return this.viewPaneContainer.openView(id, focus); } diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index f9ff5312187..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 1e07ec23451..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'; @@ -34,7 +33,7 @@ export abstract class Part extends Component implements ISerializableView { get dimension(): Dimension | undefined { return this._dimension; } protected _onDidVisibilityChange = this._register(new Emitter()); - readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; + readonly onDidVisibilityChange = this._onDidVisibilityChange.event; private parent: HTMLElement | undefined; private titleArea: HTMLElement | undefined; @@ -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 45da3780a68..ed60ba8dc65 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'; @@ -74,15 +74,15 @@ export class ViewletActivityAction extends ActivityAction { this.activity = activity; } - async run(event: any): Promise { + async run(event: unknown): Promise { if (event instanceof MouseEvent && event.button === 2) { - return false; // do not run on right click + return; // do not run on right click } // prevent accident trigger on a doubleclick (to help nervous people) const now = Date.now(); if (now > this.lastRun /* https://github.com/Microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewletActivityAction.preventDoubleClickDelay) { - return true; + return; } this.lastRun = now; @@ -93,7 +93,7 @@ export class ViewletActivityAction extends ActivityAction { if (sideBarVisible && activeViewlet?.getId() === this.activity.id) { this.logAction('hide'); this.layoutService.setSideBarHidden(true); - return true; + return; } this.logAction('show'); @@ -120,17 +120,72 @@ export class ToggleViewletAction extends Action { super(_viewlet.id, _viewlet.name); } - run(): Promise { + async run(): Promise { const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART); const activeViewlet = this.viewletService.getActiveViewlet(); // Hide sidebar if selected viewlet already visible if (sideBarVisible && activeViewlet?.getId() === this._viewlet.id) { this.layoutService.setSideBarHidden(true); - return Promise.resolve(); + return; } - return this.viewletService.openViewlet(this._viewlet.id, true); + await this.viewletService.openViewlet(this._viewlet.id, true); + } +} + +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); + } + }); } } @@ -138,11 +193,11 @@ 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, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextMenuService protected readonly contextMenuService: IContextMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(action, { draggable: false, colors, icon: true }, themeService); } @@ -226,12 +281,12 @@ class SwitchSideBarViewAction extends Action { super(id, name); } - run(offset: number): Promise { + async run(offset: number): Promise { const pinnedViewletIds = this.activityBarService.getPinnedViewletIds(); const activeViewlet = this.viewletService.getActiveViewlet(); if (!activeViewlet) { - return Promise.resolve(); + return; } let targetViewletId: string | undefined; for (let i = 0; i < pinnedViewletIds.length; i++) { @@ -240,7 +295,8 @@ class SwitchSideBarViewAction extends Action { break; } } - return this.viewletService.openViewlet(targetViewletId, true); + + await this.viewletService.openViewlet(targetViewletId, true); } } @@ -258,7 +314,7 @@ export class PreviousSideBarViewAction extends SwitchSideBarViewAction { super(id, name, viewletService, activityBarService); } - run(): Promise { + run(): Promise { return super.run(-1); } } @@ -277,7 +333,7 @@ export class NextSideBarViewAction extends SwitchSideBarViewAction { super(id, name, viewletService, activityBarService); } - run(): Promise { + run(): Promise { return super.run(1); } } @@ -286,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 67876a096ae..fa9d32a681e 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -9,24 +9,24 @@ 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 } from 'vs/workbench/browser/parts/compositeBar'; +import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; import { Dimension, addClass, removeNode } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; @@ -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 @@ -68,18 +87,18 @@ export class ActivitybarPart extends Part implements IActivityBarService { private globalActivityAction: ActivityAction | undefined; private globalActivityActionBar: ActionBar | undefined; - private globalActivity: ICompositeActivity[] = []; + private readonly globalActivity: ICompositeActivity[] = []; private customMenubar: CustomMenubarControl | undefined; private menubar: HTMLElement | undefined; private content: HTMLElement | undefined; - private cachedViewlets: ICachedViewlet[] = []; + private readonly cachedViewlets: ICachedViewlet[] = []; private compositeBar: CompositeBar; - private readonly compositeActions: Map = new Map(); + private readonly compositeActions = new Map(); - private readonly viewletDisposables: Map = new Map(); + private readonly viewletDisposables = new Map(); constructor( @IViewletService private readonly viewletService: IViewletService, @@ -92,10 +111,15 @@ 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) { if (workbenchEnvironmentService.configuration.remoteAuthority // In remote window, hide activity bar entries until registered. @@ -128,8 +152,12 @@ export class ActivitybarPart extends Part implements IActivityBarService { getContextMenuActionsForComposite: () => [], getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(), 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, 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 })); @@ -332,9 +360,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), @@ -349,7 +378,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 @@ -361,6 +400,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); } @@ -506,10 +555,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(); @@ -561,64 +615,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/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index cbc3d14705b..6e3ff5bf0f4 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -27,6 +27,10 @@ margin-bottom: auto; } +.monaco-workbench .activitybar > .content > .composite-bar-excess { + height: 100%; +} + .monaco-workbench .activitybar .menubar { width: 100%; height: 35px; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 4f824d94c74..64e86c9a814 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -11,15 +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 } from 'vs/workbench/browser/dnd'; -import { ITheme } from 'vs/platform/theme/common/themeService'; -import { Emitter, Event } from 'vs/base/common/event'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; +import { Emitter } from 'vs/base/common/event'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +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; @@ -29,25 +33,147 @@ export interface ICompositeBarItem { visible: boolean; } +export class CompositeDragAndDrop implements ICompositeDragAndDrop { + + constructor( + private viewDescriptorService: IViewDescriptorService, + private targetContainerLocation: ViewContainerLocation, + private openComposite: (id: string, focus?: boolean) => Promise, + private moveComposite: (from: string, to: string, before?: Before2D) => 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) { + // ... 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 { + const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors; + if (draggedViews.length === 1 && draggedViews[0].canMoveView) { + dragData.type = 'view'; + dragData.id = draggedViews[0].id; + } + } + } + + if (dragData.type === 'view') { + if (targetCompositeId) { + const viewToMove = this.viewDescriptorService.getViewDescriptor(dragData.id)!; + + 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(viewToMove.id, true); + } + }); + } + } else { + + } + } + } + + onDragEnter(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean { + return this.canDrop(data, targetCompositeId); + } + + onDragOver(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean { + return this.canDrop(data, targetCompositeId); + } + + private canDrop(data: CompositeDragAndDropData, targetCompositeId: string | undefined): boolean { + const dragData = data.getData(); + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + + if (dragData.type === 'composite') { + // Dragging a composite + const currentContainer = viewContainerRegistry.get(dragData.id)!; + const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer); + + // ... to the same composite location + if (currentLocation === this.targetContainerLocation) { + return true; + } + + // ... 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); + + // ... that cannot move + if (!viewDescriptor || !viewDescriptor.canMoveView) { + return false; + } + + // ... to create a view container + return true; + } + } +} + 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; getActivityAction: (compositeId: string) => ActivityAction; getCompositePinnedAction: (compositeId: string) => Action; getOnCompositeClickAction: (compositeId: string) => Action; getContextMenuActions: () => Action[]; getContextMenuActionsForComposite: (compositeId: string) => Action[]; - openComposite: (compositeId: string) => Promise; + openComposite: (compositeId: string) => Promise; getDefaultCompositeId: () => string; hidePart: () => void; } export class CompositeBar extends Widget implements ICompositeBar { + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + private dimension: Dimension | undefined; private compositeSwitcherBar: ActionBar | undefined; @@ -58,14 +184,9 @@ 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: Event = this._onDidChange.event; - constructor( items: ICompositeBarItem[], - private options: ICompositeBarOptions, + private readonly options: ICompositeBarOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { @@ -74,7 +195,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); } @@ -94,7 +214,6 @@ export class CompositeBar extends Widget implements ICompositeBar { create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); - this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { actionViewItemProvider: (action: IAction) => { if (action instanceof CompositeOverflowActivityAction) { @@ -107,6 +226,7 @@ export class CompositeBar extends Widget implements ICompositeBar { () => this.getContextMenuActions() as Action[], this.options.colors, this.options.icon, + this.options.dndHandler, this ); }, @@ -118,21 +238,27 @@ 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(addDisposableListener(parent, EventType.DROP, (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); - - const targetItem = this.model.visibleItems[this.model.visibleItems.length - 1]; - if (targetItem && targetItem.id !== draggedCompositeId) { - this.move(draggedCompositeId, targetItem.id); - } + // 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; } + + const pinnedItems = this.getPinnedComposites(); + const validDropTarget = this.options.dndHandler.onDragOver(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData); + toggleClass(parent, 'dragged-over', validDropTarget); + }, + + 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); } })); @@ -274,10 +400,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 7451ed914f9..aa0f81f9c73 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,7 +18,7 @@ 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 { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; export interface ICompositeActivity { @@ -122,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 { @@ -138,11 +138,11 @@ export class ActivityActionViewItem extends BaseActionViewItem { constructor( action: ActivityAction, options: IActivityActionViewItemOptions, - @IThemeService protected themeService: IThemeService + @IThemeService protected readonly themeService: IThemeService ) { 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)); } @@ -152,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) { @@ -165,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() : ''); } } @@ -231,7 +235,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.updateStyles(); } - private onThemeChange(theme: ITheme): void { + private onThemeChange(theme: IColorTheme): void { this.updateStyles(); } @@ -362,10 +366,8 @@ export class CompositeOverflowActivityAction extends ActivityAction { }); } - run(event: any): Promise { + async run(): Promise { this.showMenu(); - - return Promise.resolve(true); } } @@ -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 ) { @@ -440,33 +442,25 @@ class ManageExtensionAction extends Action { super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension")); } - run(id: string): Promise { + run(id: string): Promise { return this.commandService.executeCommand('_extensions.manage', id); } } -export class DraggedCompositeIdentifier { - constructor(private _compositeId: string) { } - - get id(): string { - return this._compositeId; - } -} - export class CompositeActionViewItem extends ActivityActionViewItem { private static manageExtensionAction: ManageExtensionAction; private compositeActivity: IActivity | undefined; - private compositeTransfer: LocalSelectionTransfer; constructor( private compositeActivityAction: ActivityAction, 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, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IKeybindingService private readonly keybindingService: IKeybindingService, @@ -475,8 +469,6 @@ export class CompositeActionViewItem extends ActivityActionViewItem { ) { super(compositeActivityAction, { draggable: true, colors, icon }, themeService); - this.compositeTransfer = LocalSelectionTransfer.getInstance(); - if (!CompositeActionViewItem.manageExtensionAction) { CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } @@ -521,66 +513,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) { - this.updateFromDragging(container, true); - } - } + this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.container, () => { return { type: 'composite', id: this.activity.id }; }, { + onDragOver: e => { + 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.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); + 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(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); + if (e.eventData.dataTransfer) { + e.eventData.dataTransfer.effectAllowed = 'move'; + } - this.compositeBar.move(draggedCompositeId, this.activity.id); - } - } + // 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.getAction().checked) { + if (!this.getAction().checked) { this.getAction().run(); } }))); @@ -588,11 +560,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 { @@ -664,9 +667,6 @@ export class CompositeActionViewItem extends ActivityActionViewItem { dispose(): void { super.dispose(); - - this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); - this.label.remove(); } } @@ -682,7 +682,7 @@ export class ToggleCompositePinnedAction extends Action { this.checked = !!this.activity && this.compositeBar.isPinned(this.activity.id); } - run(context: string): Promise { + async run(context: string): Promise { const id = this.activity ? this.activity.id : context; if (this.compositeBar.isPinned(id)) { @@ -690,7 +690,5 @@ export class ToggleCompositePinnedAction extends Action { } else { this.compositeBar.pin(id); } - - return Promise.resolve(true); } } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index b46b49c60ff..9d647d98524 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'; @@ -59,12 +58,13 @@ export abstract class CompositePart extends Part { protected readonly onDidCompositeClose = this._register(new Emitter()); protected toolBar: ToolBar | undefined; + protected titleLabelElement: HTMLElement | undefined; - private mapCompositeToCompositeContainer = new Map(); - private mapActionsBindingToComposite = new Map void>(); + private readonly mapCompositeToCompositeContainer = new Map(); + private readonly mapActionsBindingToComposite = new Map void>(); private activeComposite: Composite | undefined; private lastActiveCompositeId: string; - private instantiatedCompositeItems: Map; + private readonly instantiatedCompositeItems = new Map(); private titleLabel: ICompositeTitleLabel | undefined; private progressBar: ProgressBar | undefined; private contentAreaSize: Dimension | undefined; @@ -72,26 +72,25 @@ export abstract class CompositePart extends Part { private currentCompositeOpenToken: string | undefined; constructor( - private notificationService: INotificationService, - protected storageService: IStorageService, - private telemetryService: ITelemetryService, - protected contextMenuService: IContextMenuService, - protected layoutService: IWorkbenchLayoutService, - protected keybindingService: IKeybindingService, - protected instantiationService: IInstantiationService, + private readonly notificationService: INotificationService, + protected readonly storageService: IStorageService, + private readonly telemetryService: ITelemetryService, + protected readonly contextMenuService: IContextMenuService, + protected readonly layoutService: IWorkbenchLayoutService, + protected readonly keybindingService: IKeybindingService, + protected readonly instantiationService: IInstantiationService, themeService: IThemeService, protected readonly registry: CompositeRegistry, - private activeCompositeSettingsKey: string, - private defaultCompositeId: string, - private nameForTelemetry: string, - private compositeCSSClass: string, - private titleForegroundColor: string | undefined, + private readonly activeCompositeSettingsKey: string, + private readonly defaultCompositeId: string, + private readonly nameForTelemetry: string, + private readonly compositeCSSClass: string, + private readonly titleForegroundColor: string | undefined, id: string, options: IPartOptions ) { super(id, options, themeService, storageService, layoutService); - this.instantiatedCompositeItems = new Map(); this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId); } @@ -402,6 +401,7 @@ export abstract class CompositePart extends Part { protected createTitleLabel(parent: HTMLElement): ICompositeTitleLabel { const titleContainer = append(parent, $('.title-label')); const titleLabel = append(titleContainer, $('h2')); + this.titleLabelElement = titleLabel; const $this = this; return { diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 583bdb727a7..e57afd9b033 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'; @@ -16,6 +16,9 @@ import { Event } from 'vs/base/common/event'; import { isEmptyObject } from 'vs/base/common/types'; import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { MementoObject } from 'vs/workbench/common/memento'; +import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { isLinux } from 'vs/base/common/platform'; +import { indexOfPath } from 'vs/base/common/extpath'; /** * The base class of editors in the workbench. Editors register themselves for specific editor inputs. @@ -30,21 +33,25 @@ import { MementoObject } from 'vs/workbench/common/memento'; * * 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; readonly minimumHeight = DEFAULT_EDITOR_MIN_DIMENSIONS.height; readonly maximumHeight = DEFAULT_EDITOR_MAX_DIMENSIONS.height; - readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined> = Event.None; + readonly onDidSizeConstraintsChange = Event.None; protected _input: EditorInput | undefined; + get input(): EditorInput | undefined { return this._input; } + protected _options: EditorOptions | undefined; + get options(): EditorOptions | undefined { return this._options; } private _group?: IEditorGroup; + get group(): IEditorGroup | undefined { return this._group; } constructor( id: string, @@ -55,18 +62,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. @@ -176,17 +171,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 { @@ -249,9 +240,37 @@ export class EditorMemento implements IEditorMemento { } } + moveEditorState(source: URI, target: URI): void { + const cache = this.doLoad(); + + const cacheKeys = cache.keys(); + for (const cacheKey of cacheKeys) { + const resource = URI.parse(cacheKey); + + if (!isEqualOrParent(resource, source)) { + continue; // not matching our resource + } + + // Determine new resulting target resource + let targetResource: URI; + if (source.toString() === resource.toString()) { + targetResource = target; // file got moved + } else { + const index = indexOfPath(resource.path, source.path, !isLinux); + targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved + } + + const value = cache.get(cacheKey); + if (value) { + cache.delete(cacheKey); + cache.set(targetResource.toString(), value); + } + } + } + private doGetResource(resourceOrEditor: URI | EditorInput): URI | undefined { if (resourceOrEditor instanceof EditorInput) { - return resourceOrEditor.getResource(); + return resourceOrEditor.resource; } return resourceOrEditor; 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 db819929569..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)); } @@ -231,7 +239,7 @@ export class BreadcrumbsControl { input = input.master; } - if (!input || !input.getResource() || !this._fileService.canHandleResource(input.getResource()!)) { + if (!input || !input.resource || !this._fileService.canHandleResource(input.resource!)) { // cleanup and return when there is no input or when // we cannot handle this input this._ckBreadcrumbsPossible.set(false); @@ -247,7 +255,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible.set(true); this._ckBreadcrumbsPossible.set(true); - const uri = input.getResource()!; + const uri = input.resource; const editor = this._getActiveCodeEditor(); const model = new EditorBreadcrumbsModel( uri, editor, @@ -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; } @@ -470,7 +486,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsActive.set(value); } - private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void { + private _revealInEditor(event: IBreadcrumbsItemEvent, element: BreadcrumbElement, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void { if (element instanceof FileElement) { if (element.kind === FileKind.FILE) { // open file in any editor @@ -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..182ea661d7f 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -24,10 +24,12 @@ 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 { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter, OutlineAccessibilityProvider } 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'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker { return element instanceof FileElement @@ -69,7 +71,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 +88,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 +99,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 }; @@ -271,6 +273,13 @@ class FileNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + + getAriaLabel(element: IWorkspaceFolder | IFileStat): string | null { + return element.name; + } +} + class FileFilter implements ITreeFilter { private readonly _cachedExpressions = new Map(); @@ -351,7 +360,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, ) { @@ -373,16 +382,24 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { const labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */); this._disposables.add(labels); - return >this._instantiationService.createInstance(WorkbenchAsyncDataTree, 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { - multipleSelectionSupport: false, - sorter: new FileSorter(), - filter: this._instantiationService.createInstance(FileFilter), - identityProvider: new FileIdentityProvider(), - keyboardNavigationLabelProvider: new FileNavigationLabelProvider(), - overrideStyles: { - listBackground: breadcrumbsPickerBackground - } - }); + return >this._instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'BreadcrumbsFilePicker', + container, + new FileVirtualDelegate(), + [this._instantiationService.createInstance(FileRenderer, labels)], + this._instantiationService.createInstance(FileDataSource), + { + multipleSelectionSupport: false, + sorter: new FileSorter(), + filter: this._instantiationService.createInstance(FileFilter), + identityProvider: new FileIdentityProvider(), + keyboardNavigationLabelProvider: new FileNavigationLabelProvider(), + accessibilityProvider: this._instantiationService.createInstance(FileAccessibilityProvider), + overrideStyles: { + listBackground: breadcrumbsPickerBackground + }, + }); } _setInput(element: BreadcrumbElement): Promise { @@ -433,7 +450,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); @@ -456,6 +473,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { sorter: this._outlineComparator, identityProvider: new OutlineIdentityProvider(), keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), + accessibilityProvider: new OutlineAccessibilityProvider(), filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs') } ); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 2012c00b543..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 } from 'vs/platform/contextkey/common/contextkey'; +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( @@ -130,7 +129,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { const untitledTextEditorInput = editorInput; - let resource = untitledTextEditorInput.getResource(); + let resource = untitledTextEditorInput.resource; if (untitledTextEditorInput.model.hasAssociatedFilePath) { resource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); // untitled with associated file path use the local schema } @@ -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: any): boolean { - const entry = this.getEntry(context); - - return !!entry; - } - - getActions(context: any): 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 } @@ -511,7 +446,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands. interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; } -function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | undefined, order: number, alternative?: IEditorToolItem, precondition?: ContextKeyExpr | undefined): void { +function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpression | undefined, order: number, alternative?: IEditorToolItem, precondition?: ContextKeyExpression | undefined): void { const item: IMenuItem = { command: { id: primary.id, diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 371455df9d0..255a3b71784 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,18 +3,17 @@ * 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'; import { Dimension } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; -import { assign } from 'vs/base/common/objects'; import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; 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 +29,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { highlightModifiedTabs: false, tabCloseButton: 'right', tabSizing: 'fit', + titleScrollbarSizing: 'default', focusRecentEditorAfterClose: true, showIcons: true, enablePreview: true, @@ -46,7 +46,7 @@ export function impactsEditorPartOptions(event: IConfigurationChangeEvent): bool } export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEditorPartOptions { - const options: IEditorPartOptions = assign(Object.create(null), DEFAULT_EDITOR_PART_OPTIONS); + const options = { ...DEFAULT_EDITOR_PART_OPTIONS }; if (!config || !config.workbench) { return options; @@ -57,13 +57,17 @@ export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEd } if (config.workbench.editor) { - assign(options, config.workbench.editor); + Object.assign(options, config.workbench.editor); } return options; } export interface IEditorOpeningEvent extends IEditorIdentifier { + + /** + * The options used when opening the editor. + */ options?: IEditorOptions; /** @@ -73,7 +77,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 +130,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 +164,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 7fcca6a05f2..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 { @@ -37,7 +33,7 @@ export class ExecuteCommandAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { return this.commandService.executeCommand(this.commandId, this.commandArgs); } } @@ -71,10 +67,8 @@ export class BaseSplitEditorAction extends Action { })); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { splitEditor(this.editorGroupService, this.direction, context); - - return Promise.resolve(true); } } @@ -183,7 +177,7 @@ export class JoinTwoGroupsAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); @@ -198,12 +192,10 @@ export class JoinTwoGroupsAction extends Action { if (targetGroup && sourceGroup !== targetGroup) { this.editorGroupService.mergeGroup(sourceGroup, targetGroup); - return Promise.resolve(true); + break; } } } - - return Promise.resolve(true); } } @@ -220,10 +212,8 @@ export class JoinAllGroupsAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(): Promise { mergeAllGroups(this.editorGroupService); - - return Promise.resolve(true); } } @@ -240,11 +230,9 @@ export class NavigateBetweenGroupsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true); nextGroup.focus(); - - return Promise.resolve(true); } } @@ -261,10 +249,8 @@ export class FocusActiveGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.activeGroup.focus(); - - return Promise.resolve(true); } } @@ -279,13 +265,11 @@ export abstract class BaseFocusGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true); if (group) { group.focus(); } - - return Promise.resolve(true); } } @@ -401,64 +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'; - } - - run(context: any): Promise { - const entry = toEditorQuickOpenEntry(context); - if (entry) { - const input = entry.getInput(); - if (input) { - if (input instanceof EditorInput) { - return this.editorService.openEditor(input, entry.getOptions(), SIDE_GROUP); - } - - const resourceInput = input as IResourceInput; - resourceInput.options = mixin(resourceInput.options, entry.getOptions()); - - return this.editorService.openEditor(resourceInput, SIDE_GROUP); - } - } - - return Promise.resolve(false); - } -} - -export function toEditorQuickOpenEntry(element: any): 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'; @@ -472,7 +398,7 @@ export class CloseEditorAction extends Action { super(id, label, 'codicon-close'); } - run(context?: IEditorCommandsContext): Promise { + run(context?: IEditorCommandsContext): Promise { return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, undefined, context); } } @@ -490,7 +416,7 @@ export class CloseOneEditorAction extends Action { super(id, label, 'codicon-close'); } - run(context?: IEditorCommandsContext): Promise { + async run(context?: IEditorCommandsContext): Promise { let group: IEditorGroup | undefined; let editorIndex: number | undefined; if (context) { @@ -517,8 +443,6 @@ export class CloseOneEditorAction extends Action { if (group.activeEditor) { return group.closeEditor(group.activeEditor); } - - return Promise.resolve(false); } } @@ -535,11 +459,11 @@ export class RevertAndCloseEditorAction extends Action { super(id, label); } - async run(): Promise { - const activeControl = this.editorService.activeControl; - if (activeControl) { - const editor = activeControl.input; - const group = activeControl.group; + async run(): Promise { + 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 { @@ -554,8 +478,6 @@ export class RevertAndCloseEditorAction extends Action { group.closeEditor(editor); } - - return true; } } @@ -573,13 +495,11 @@ export class CloseLeftEditorsInGroupAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { const { group, editor } = getTarget(this.editorService, this.editorGroupService, context); if (group && editor) { return group.closeEditors({ direction: CloseDirection.LEFT, except: editor }); } - - return Promise.resolve(false); } } @@ -620,7 +540,7 @@ export abstract class BaseCloseAllAction extends Action { return groupsToClose; } - async run(): Promise { + async run(): Promise { // Just close all if there are no dirty editors if (!this.workingCopyService.hasDirty) { @@ -657,24 +577,23 @@ 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; } - let saveOrRevert: boolean; if (confirm === ConfirmResult.DONT_SAVE) { - saveOrRevert = await this.editorService.revertAll({ soft: true, includeUntitled: true }); + await this.editorService.revertAll({ soft: true, includeUntitled: true }); } else { - saveOrRevert = await this.editorService.saveAll({ reason: SaveReason.EXPLICIT, includeUntitled: true }); + await this.editorService.saveAll({ reason: SaveReason.EXPLICIT, includeUntitled: true }); } - if (saveOrRevert) { + if (!this.workingCopyService.hasDirty) { return this.doCloseAll(); } } - protected abstract doCloseAll(): Promise; + protected abstract doCloseAll(): Promise; } export class CloseAllEditorsAction extends BaseCloseAllAction { @@ -693,8 +612,8 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { super(id, label, 'codicon-close-all', workingCopyService, fileDialogService, editorGroupService, editorService); } - protected doCloseAll(): Promise { - return Promise.all(this.groupsToClose.map(g => g.closeAllEditors())); + protected async doCloseAll(): Promise { + await Promise.all(this.groupsToClose.map(g => g.closeAllEditors())); } } @@ -714,7 +633,7 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction { super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService); } - protected async doCloseAll(): Promise { + protected async doCloseAll(): Promise { await Promise.all(this.groupsToClose.map(group => group.closeAllEditors())); this.groupsToClose.forEach(group => this.editorGroupService.removeGroup(group)); @@ -734,11 +653,11 @@ export class CloseEditorsInOtherGroupsAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup; - return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => { + await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => { if (groupToSkip && g.id === groupToSkip.id) { - return Promise.resolve(); + return; } return g.closeAllEditors(); @@ -760,13 +679,11 @@ export class CloseEditorInAllGroupsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const activeEditor = this.editorService.activeEditor; if (activeEditor) { - return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor))); + await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor))); } - - return Promise.resolve(); } } @@ -781,7 +698,7 @@ export class BaseMoveGroupAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); @@ -795,8 +712,6 @@ export class BaseMoveGroupAction extends Action { this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction); } } - - return Promise.resolve(true); } private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup | undefined { @@ -892,10 +807,8 @@ export class MinimizeOtherGroupsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); - - return Promise.resolve(false); } } @@ -908,10 +821,8 @@ export class ResetGroupSizesAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN); - - return Promise.resolve(false); } } @@ -924,10 +835,8 @@ export class ToggleGroupSizesAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE); - - return Promise.resolve(false); } } @@ -946,13 +855,11 @@ export class MaximizeGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.editorService.activeEditor) { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); this.layoutService.setSideBarHidden(true); } - - return Promise.resolve(false); } } @@ -967,23 +874,21 @@ export abstract class BaseNavigateEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const result = this.navigate(); if (!result) { - return Promise.resolve(false); + return; } const { groupId, editor } = result; if (!editor) { - return Promise.resolve(false); + return; } const group = this.editorGroupService.getGroup(groupId); if (group) { - return group.openEditor(editor); + await group.openEditor(editor); } - - return Promise.resolve(); } protected abstract navigate(): IEditorIdentifier | undefined; @@ -1158,10 +1063,8 @@ export class NavigateForwardAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.forward(); - - return Promise.resolve(); } } @@ -1174,10 +1077,8 @@ export class NavigateBackwardsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.back(); - - return Promise.resolve(); } } @@ -1190,10 +1091,8 @@ export class NavigateToLastEditLocationAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openLastEditLocation(); - - return Promise.resolve(); } } @@ -1206,10 +1105,8 @@ export class NavigateLastAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.last(); - - return Promise.resolve(); } } @@ -1226,10 +1123,8 @@ export class ReopenClosedEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.reopenLastClosedEditor(); - - return Promise.resolve(false); } } @@ -1247,82 +1142,94 @@ export class ClearRecentFilesAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { // Clear global recently opened this.workspacesService.clearRecentlyOpened(); // Clear workspace specific recently opened this.historyService.clearRecentlyOpened(); - - return Promise.resolve(false); } } -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); } - run(): Promise { + async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } }); - - return Promise.resolve(true); + 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"); @@ -1330,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"); @@ -1345,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"); @@ -1360,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"); @@ -1375,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"); @@ -1390,18 +1297,16 @@ 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); } - run(): Promise { + async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } }); - - return Promise.resolve(true); + this.quickInputService.quickAccess.show('', { quickNavigateConfiguration: { keybindings } }); } } @@ -1418,10 +1323,8 @@ export class OpenNextRecentlyUsedEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openNextRecentlyUsedEditor(); - - return Promise.resolve(); } } @@ -1438,10 +1341,8 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openPreviouslyUsedEditor(); - - return Promise.resolve(); } } @@ -1459,10 +1360,8 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id); - - return Promise.resolve(); } } @@ -1480,10 +1379,8 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id); - - return Promise.resolve(); } } @@ -1500,12 +1397,10 @@ export class ClearEditorHistoryAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { // Editor history this.historyService.clear(); - - return Promise.resolve(true); } } @@ -1772,10 +1667,8 @@ export class BaseCreateEditorGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true }); - - return Promise.resolve(true); } } 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 eeb80e7449b..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 { @@ -84,7 +81,7 @@ function registerActiveEditorMoveCommand(): void { weight: KeybindingWeight.WorkbenchContrib, when: EditorContextKeys.editorTextFocus, primary: 0, - handler: (accessor, args: any) => moveActiveEditor(args, accessor), + handler: (accessor, args) => moveActiveEditor(args, accessor), description: { description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"), args: [ @@ -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 11f5d5a0a04..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 }); @@ -542,7 +538,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private toEditorTelemetryDescriptor(editor: EditorInput): object { const descriptor = editor.getTelemetryDescriptor(); - const resource = editor.getResource(); + const resource = editor.resource; const path = resource ? resource.scheme === Schemas.file ? resource.fsPath : resource.path : undefined; if (resource && path) { descriptor['resource'] = { mimeType: guessMimeTypes(resource).join(', '), scheme: resource.scheme, ext: extname(resource), path: hash(path) }; @@ -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 @@ -1580,7 +1578,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { get maximumHeight(): number { return this.editorControl.maximumHeight; } private _onDidChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); - readonly onDidChange: Event<{ width: number; height: number; } | undefined> = this._onDidChange.event; + readonly onDidChange = this._onDidChange.event; layout(width: number, height: number): void { this.dimension = new Dimension(width, height); @@ -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..d708c9e414f 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -13,14 +13,12 @@ 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'; import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { assign } from 'vs/base/common/objects'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { EditorDropTarget, EditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget'; @@ -32,6 +30,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; @@ -175,7 +174,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro const newPartOptions = getEditorPartOptions(this.configurationService.getValue()); this.enforcedPartOptions.forEach(enforcedPartOptions => { - assign(newPartOptions, enforcedPartOptions); // check for overrides + Object.assign(newPartOptions, enforcedPartOptions); // check for overrides }); this._partOptions = newPartOptions; @@ -210,7 +209,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 +825,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; } @@ -836,7 +849,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } isLayoutCentered(): boolean { - return this.centeredLayoutWidget.isActive(); + if (this.centeredLayoutWidget) { + return this.centeredLayoutWidget.isActive(); + } + + return false; } private doCreateGridControl(options?: IEditorPartCreationOptions): void { 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 09929dceb86..92e75be5d6a 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'; @@ -144,6 +144,7 @@ class StateChange { encoding: boolean = false; EOL: boolean = false; tabFocusMode: boolean = false; + columnSelectionMode: boolean = false; screenReaderMode: boolean = false; metadata: boolean = false; @@ -154,6 +155,7 @@ class StateChange { this.encoding = this.encoding || other.encoding; this.EOL = this.EOL || other.EOL; this.tabFocusMode = this.tabFocusMode || other.tabFocusMode; + this.columnSelectionMode = this.columnSelectionMode || other.columnSelectionMode; this.screenReaderMode = this.screenReaderMode || other.screenReaderMode; this.metadata = this.metadata || other.metadata; } @@ -165,21 +167,23 @@ class StateChange { || this.encoding || this.EOL || this.tabFocusMode + || this.columnSelectionMode || this.screenReaderMode || this.metadata; } } -interface StateDelta { - selectionStatus?: string; - mode?: string; - encoding?: string; - EOL?: string; - indentation?: string; - tabFocusMode?: boolean; - screenReaderMode?: boolean; - metadata?: string | undefined; -} +type StateDelta = ( + { type: 'selectionStatus'; selectionStatus: string | undefined; } + | { type: 'mode'; mode: string | undefined; } + | { type: 'encoding'; encoding: string | undefined; } + | { type: 'EOL'; EOL: string | undefined; } + | { type: 'indentation'; indentation: string | undefined; } + | { type: 'tabFocusMode'; tabFocusMode: boolean; } + | { type: 'columnSelectionMode'; columnSelectionMode: boolean; } + | { type: 'screenReaderMode'; screenReaderMode: boolean; } + | { type: 'metadata'; metadata: string | undefined; } +); class State { @@ -201,6 +205,9 @@ class State { private _tabFocusMode: boolean | undefined; get tabFocusMode(): boolean | undefined { return this._tabFocusMode; } + private _columnSelectionMode: boolean | undefined; + get columnSelectionMode(): boolean | undefined { return this._columnSelectionMode; } + private _screenReaderMode: boolean | undefined; get screenReaderMode(): boolean | undefined { return this._screenReaderMode; } @@ -210,56 +217,63 @@ class State { update(update: StateDelta): StateChange { const change = new StateChange(); - if ('selectionStatus' in update) { + if (update.type === 'selectionStatus') { if (this._selectionStatus !== update.selectionStatus) { this._selectionStatus = update.selectionStatus; change.selectionStatus = true; } } - if ('indentation' in update) { + if (update.type === 'indentation') { if (this._indentation !== update.indentation) { this._indentation = update.indentation; change.indentation = true; } } - if ('mode' in update) { + if (update.type === 'mode') { if (this._mode !== update.mode) { this._mode = update.mode; change.mode = true; } } - if ('encoding' in update) { + if (update.type === 'encoding') { if (this._encoding !== update.encoding) { this._encoding = update.encoding; change.encoding = true; } } - if ('EOL' in update) { + if (update.type === 'EOL') { if (this._EOL !== update.EOL) { this._EOL = update.EOL; change.EOL = true; } } - if ('tabFocusMode' in update) { + if (update.type === 'tabFocusMode') { if (this._tabFocusMode !== update.tabFocusMode) { this._tabFocusMode = update.tabFocusMode; change.tabFocusMode = true; } } - if ('screenReaderMode' in update) { + if (update.type === 'columnSelectionMode') { + if (this._columnSelectionMode !== update.columnSelectionMode) { + this._columnSelectionMode = update.columnSelectionMode; + change.columnSelectionMode = true; + } + } + + if (update.type === 'screenReaderMode') { if (this._screenReaderMode !== update.screenReaderMode) { this._screenReaderMode = update.screenReaderMode; change.screenReaderMode = true; } } - if ('metadata' in update) { + if (update.type === 'metadata') { if (this._metadata !== update.metadata) { this._metadata = update.metadata; change.metadata = true; @@ -279,6 +293,7 @@ const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF"); export class EditorStatus extends Disposable implements IWorkbenchContribution { private readonly tabFocusModeElement = this._register(new MutableDisposable()); + private readonly columnSelectionModeElement = this._register(new MutableDisposable()); private readonly screenRedearModeElement = this._register(new MutableDisposable()); private readonly indentationElement = this._register(new MutableDisposable()); private readonly selectionElement = this._register(new MutableDisposable()); @@ -314,8 +329,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private registerListeners(): void { this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); - this._register(this.textFileService.untitled.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); - this._register(this.textFileService.files.onDidChangeEncoding(m => this.onResourceEncodingChange((m.resource)))); + this._register(this.textFileService.untitled.onDidChangeEncoding(model => this.onResourceEncodingChange(model.resource))); + this._register(this.textFileService.files.onDidChangeEncoding(model => this.onResourceEncodingChange((model.resource)))); this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); } @@ -348,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") }]); } @@ -358,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(); } }; @@ -399,6 +414,22 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } } + private updateColumnSelectionModeElement(visible: boolean): void { + if (visible) { + if (!this.columnSelectionModeElement.value) { + this.columnSelectionModeElement.value = this.statusbarService.addEntry({ + text: nls.localize('columnSelectionModeEnabled', "Column Selection"), + tooltip: nls.localize('disableColumnSelectionMode', "Disable Column Selection Mode"), + command: 'editor.action.toggleColumnSelection', + backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), + color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) + }, 'status.editor.columnSelectionMode', nls.localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8); + } + } else { + this.columnSelectionModeElement.clear(); + } + } + private updateScreenReaderModeElement(visible: boolean): void { if (visible) { if (!this.screenRedearModeElement.value) { @@ -423,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' }; @@ -541,9 +572,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private doRenderNow(changed: StateChange): void { this.updateTabFocusModeElement(!!this.state.tabFocusMode); + this.updateColumnSelectionModeElement(!!this.state.columnSelectionMode); this.updateScreenReaderModeElement(!!this.state.screenReaderMode); this.updateIndentationElement(this.state.indentation); - this.updateSelectionElement(this.state.selectionStatus && !this.state.screenReaderMode ? this.state.selectionStatus : undefined); + this.updateSelectionElement(this.state.selectionStatus); this.updateEncodingElement(this.state.encoding); this.updateEOLElement(this.state.EOL ? this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF : undefined); this.updateModeElement(this.state.mode); @@ -576,17 +608,18 @@ 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); this.onScreenReaderModeChange(activeCodeEditor); 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 @@ -597,6 +630,9 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { // Hook Listener for Configuration changes this.activeEditorListeners.add(activeCodeEditor.onDidChangeConfiguration((event: ConfigurationChangedEvent) => { + if (event.hasChanged(EditorOption.columnSelection)) { + this.onColumnSelectionModeChange(activeCodeEditor); + } if (event.hasChanged(EditorOption.accessibilitySupport)) { this.onScreenReaderModeChange(activeCodeEditor); } @@ -636,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(() => { @@ -665,14 +701,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private onModeChange(editorWidget: ICodeEditor | undefined, editorInput: IEditorInput | undefined): void { - let info: StateDelta = { mode: undefined }; + let info: StateDelta = { type: 'mode', mode: undefined }; // We only support text based editors if (editorWidget && editorInput && toEditorWithModeSupport(editorInput)) { const textModel = editorWidget.getModel(); if (textModel) { const modeId = textModel.getLanguageIdentifier().language; - info = { mode: withNullAsUndefined(this.modeService.getLanguageName(modeId)) }; + info.mode = withNullAsUndefined(this.modeService.getLanguageName(modeId)); } } @@ -680,7 +716,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private onIndentationChange(editorWidget: ICodeEditor | undefined): void { - const update: StateDelta = { indentation: undefined }; + const update: StateDelta = { type: 'indentation', indentation: undefined }; if (editorWidget) { const model = editorWidget.getModel(); @@ -697,8 +733,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(update); } - private onMetadataChange(editor: IBaseEditor | undefined): void { - const update: StateDelta = { metadata: undefined }; + private onMetadataChange(editor: IEditorPane | undefined): void { + const update: StateDelta = { type: 'metadata', metadata: undefined }; if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) { update.metadata = editor.getMetadata(); @@ -707,6 +743,16 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(update); } + private onColumnSelectionModeChange(editorWidget: ICodeEditor | undefined): void { + const info: StateDelta = { type: 'columnSelectionMode', columnSelectionMode: false }; + + if (editorWidget && editorWidget.getOption(EditorOption.columnSelection)) { + info.columnSelectionMode = true; + } + + this.updateState(info); + } + private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void { let screenReaderMode = false; @@ -730,7 +776,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.screenReaderNotification.close(); } - this.updateState({ screenReaderMode: screenReaderMode }); + this.updateState({ type: 'screenReaderMode', screenReaderMode: screenReaderMode }); } private onSelectionChange(editorWidget: ICodeEditor | undefined): void { @@ -770,11 +816,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } } - this.updateState({ selectionStatus: this.getSelectionLabel(info) }); + this.updateState({ type: 'selectionStatus', selectionStatus: this.getSelectionLabel(info) }); } private onEOLChange(editorWidget: ICodeEditor | undefined): void { - const info: StateDelta = { EOL: undefined }; + const info: StateDelta = { type: 'EOL', EOL: undefined }; if (editorWidget && !editorWidget.getOption(EditorOption.readOnly)) { const codeEditorModel = editorWidget.getModel(); @@ -786,12 +832,12 @@ 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; } - const info: StateDelta = { encoding: undefined }; + const info: StateDelta = { type: 'encoding', encoding: undefined }; // We only support text based editors that have a model associated // This ensures we do not show the encoding picker while an editor @@ -813,27 +859,27 @@ 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 } } } private onTabFocusModeChange(): void { - const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() }; + const info: StateDelta = { type: 'tabFocusMode', tabFocusMode: TabFocus.getTabFocusMode() }; 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; } } @@ -1000,13 +1046,14 @@ export class ChangeModeAction extends Action { super(actionId, actionLabel); } - async run(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + async run(): Promise { + 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; @@ -1090,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; } @@ -1162,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 { @@ -1200,17 +1247,19 @@ export class ChangeEOLAction extends Action { super(actionId, actionLabel); } - async run(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + async run(): Promise { + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); + if (!activeTextEditorControl) { + await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return; } if (this.editorService.activeEditor?.isReadonly()) { - return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + await this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + return; } - let textModel = activeTextEditorWidget.getModel(); + let textModel = activeTextEditorControl.getModel(); const EOLOptions: IChangeEOLEntry[] = [ { label: nlsEOLLF, eol: EndOfLineSequence.LF }, @@ -1221,10 +1270,12 @@ 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(); textModel.pushEOL(eol.eol); + textModel.pushStackElement(); } } } @@ -1247,19 +1298,22 @@ export class ChangeEncodingAction extends Action { super(actionId, actionLabel); } - async run(): Promise { - if (!getCodeEditor(this.editorService.activeTextEditorWidget)) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + async run(): Promise { + 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) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + 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) { - return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); + await this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); + return; } const saveWithEncodingPick: IQuickPickItem = { label: nls.localize('saveWithEncoding', "Save with Encoding") }; @@ -1277,10 +1331,10 @@ export class ChangeEncodingAction extends Action { } } - let action: IQuickPickItem; + let action: IQuickPickItem | undefined; 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 }); @@ -1290,11 +1344,11 @@ 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 null; // encoding detection only possible for resources the file service can handle or that are untitled + return; // encoding detection only possible for resources the file service can handle or that are untitled } let guessedEncoding: string | undefined = undefined; @@ -1355,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/workbench/browser/parts/editor/media/editorquickaccess.css b/src/vs/workbench/browser/parts/editor/media/editorquickaccess.css new file mode 100644 index 00000000000..89390b0c7e5 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/editorquickaccess.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-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..cbfb63cd198 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 { @@ -99,7 +99,6 @@ export class NoTabsTitleControl extends TitleControl { } private onTitleClick(e: MouseEvent | GestureEvent): void { - if (e instanceof MouseEvent) { // Close editor on middle mouse click if (e.button === 1 /* Middle Button */) { @@ -110,11 +109,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 +259,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 +274,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 4c52be6ca69..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); } } @@ -60,10 +60,10 @@ export class RangeHighlightDecorations extends Disposable { private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined { const activeEditor = this.editorService.activeEditor; - const resource = activeEditor && activeEditor.getResource(); + 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..8ce7c77ccb6 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,20 +40,28 @@ 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; description?: string; title?: string; + ariaLabel?: string; } 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 +77,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 +90,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 +159,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 +172,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 +232,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 +431,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 { @@ -466,7 +511,7 @@ export class TabsTitleControl extends TitleControl { const tabContainer = document.createElement('div'); tabContainer.draggable = true; tabContainer.tabIndex = 0; - tabContainer.setAttribute('role', 'presentation'); // cannot use role "tab" here due to https://github.com/Microsoft/vscode/issues/8659 + tabContainer.setAttribute('role', 'tab'); addClass(tabContainer, 'tab'); // Gesture Support @@ -798,7 +843,8 @@ export class TabsTitleControl extends TitleControl { editor, name: editor.getName(), description: editor.getDescription(verbosity), - title: withNullAsUndefined(editor.getTitle(Verbosity.LONG)) + title: withNullAsUndefined(editor.getTitle(Verbosity.LONG)), + ariaLabel: editor.getTitle(Verbosity.SHORT) })); // Shorten labels as needed @@ -869,7 +915,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]; @@ -943,15 +989,19 @@ export class TabsTitleControl extends TitleControl { const description = tabLabel.description || ''; const title = tabLabel.title || ''; - // Container - tabContainer.setAttribute('aria-label', `${name}, tab`); + if (tabLabel.ariaLabel) { + tabContainer.setAttribute('aria-label', tabLabel.ariaLabel); + } 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 +1286,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 036cb7b0ea0..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'; @@ -30,12 +30,11 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo import { CancellationToken } from 'vs/base/common/cancellation'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; /** * 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; @@ -49,8 +48,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IClipboardService private clipboardService: IClipboardService + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } @@ -68,7 +66,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor { - return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, this.clipboardService); + return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration); } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { @@ -162,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(); } @@ -321,8 +319,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { let modified: URI | undefined; if (modelOrInput instanceof DiffEditorInput) { - original = modelOrInput.originalInput.getResource(); - modified = modelOrInput.modifiedInput.getResource(); + original = modelOrInput.originalInput.resource; + modified = modelOrInput.modifiedInput.resource; } else { original = modelOrInput.original.uri; modified = modelOrInput.modified.uri; diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 99dbe8e9106..e41206a40d9 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -5,12 +5,12 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { distinct, deepClone, assign } from 'vs/base/common/objects'; +import { distinct, deepClone } from 'vs/base/common/objects'; 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'; @@ -58,7 +58,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { this.editorMemento = this.getEditorMemento(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); this._register(this.textResourceConfigurationService.onDidChangeConfiguration(e => { - const resource = this.getResource(); + const resource = this.getActiveResource(); const value = resource ? this.textResourceConfigurationService.getValue(resource) : undefined; return this.handleConfigurationChangeEvent(value); @@ -93,7 +93,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { // Specific editor options always overwrite user configuration const editorConfiguration: IEditorOptions = isObject(configuration.editor) ? deepClone(configuration.editor) : Object.create(null); - assign(editorConfiguration, this.getConfigurationOverrides()); + Object.assign(editorConfiguration, this.getConfigurationOverrides()); // ARIA label editorConfiguration.ariaLabel = this.computeAriaLabel(); @@ -130,7 +130,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { // Editor for Text this.editorContainer = parent; - this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.textResourceConfigurationService.getValue(this.getResource())))); + this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.textResourceConfigurationService.getValue(this.getActiveResource())))); // Model & Language changes const codeEditor = getCodeEditor(this.editorControl); @@ -204,9 +204,6 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return this.editorControl; } - /** - * Saves the text editor view state for the given resource. - */ protected saveTextEditorViewState(resource: URI): void { const editorViewState = this.retrieveTextEditorViewState(resource); if (!editorViewState || !this.group) { @@ -217,7 +214,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { } getViewState(): IEditorViewState | undefined { - const resource = this.input?.getResource(); + const resource = this.input?.resource; if (resource) { return withNullAsUndefined(this.retrieveTextEditorViewState(resource)); } @@ -248,25 +245,23 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return control.saveViewState(); } - /** - * Clears the text editor view state for the given resources. - */ + protected loadTextEditorViewState(resource: URI): IEditorViewState | undefined { + return this.group ? this.editorMemento.loadEditorState(this.group, resource) : undefined; + } + + protected moveTextEditorViewState(source: URI, target: URI): void { + return this.editorMemento.moveEditorState(source, target); + } + protected clearTextEditorViewState(resources: URI[], group?: IEditorGroup): void { resources.forEach(resource => { this.editorMemento.clearEditorState(resource, group); }); } - /** - * Loads the text editor view state for the given resource and returns it. - */ - protected loadTextEditorViewState(resource: URI): IEditorViewState | undefined { - return this.group ? this.editorMemento.loadEditorState(this.group, resource) : undefined; - } - private updateEditorConfiguration(configuration?: IEditorConfiguration): void { if (!configuration) { - const resource = this.getResource(); + const resource = this.getActiveResource(); if (resource) { configuration = this.textResourceConfigurationService.getValue(resource); } @@ -292,7 +287,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { } } - private getResource(): URI | undefined { + private getActiveResource(): URI | undefined { const codeEditor = getCodeEditor(this.editorControl); if (codeEditor) { const model = codeEditor.getModel(); @@ -302,7 +297,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { } if (this.input) { - return this.input.getResource(); + return this.input.resource; } return undefined; diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 3909f3aa753..4937fb0b692 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -101,7 +101,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { private restoreTextResourceEditorViewState(editor: EditorInput, control: IEditor) { if (editor instanceof UntitledTextEditorInput || editor instanceof ResourceEditorInput) { - const viewState = this.loadTextEditorViewState(editor.getResource()); + const viewState = this.loadTextEditorViewState(editor.resource); if (viewState) { control.restoreViewState(viewState); } @@ -111,7 +111,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { protected getAriaLabel(): string { let ariaLabel: string; - const inputName = this.input instanceof UntitledTextEditorInput ? basenameOrAuthority(this.input.getResource()) : this.input?.getName(); + const inputName = this.input instanceof UntitledTextEditorInput ? basenameOrAuthority(this.input.resource) : this.input?.getName(); if (this.input?.isReadonly()) { ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0} readonly editor", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly editor"); } else { @@ -163,7 +163,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { return; // only enabled for untitled and resource inputs } - const resource = input.getResource(); + const resource = input.resource; // Clear view state if input is disposed if (input.isDisposed()) { 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/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index f6e1ee43461..5318fd1ace0 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -26,10 +26,8 @@ export class ClearNotificationAction extends Action { super(id, label, 'codicon-close'); } - run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(CLEAR_NOTIFICATION, notification); - - return Promise.resolve(); } } @@ -46,10 +44,8 @@ export class ClearAllNotificationsAction extends Action { super(id, label, 'codicon-clear-all'); } - run(notification: INotificationViewItem): Promise { + async run(): Promise { this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS); - - return Promise.resolve(); } } @@ -63,13 +59,11 @@ export class HideNotificationsCenterAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'codicon-close'); + super(id, label, 'codicon-chevron-down'); } - run(notification: INotificationViewItem): Promise { + async run(): Promise { this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER); - - return Promise.resolve(); } } @@ -86,10 +80,8 @@ export class ExpandNotificationAction extends Action { super(id, label, 'codicon-chevron-up'); } - run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(EXPAND_NOTIFICATION, notification); - - return Promise.resolve(); } } @@ -106,10 +98,8 @@ export class CollapseNotificationAction extends Action { super(id, label, 'codicon-chevron-down'); } - run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification); - - return Promise.resolve(); } } @@ -140,7 +130,7 @@ export class CopyNotificationMessageAction extends Action { super(id, label); } - run(notification: INotificationViewItem): Promise { + run(notification: INotificationViewItem): Promise { return this.clipboardService.writeText(notification.message.raw); } } @@ -154,7 +144,7 @@ export class NotificationActionRunner extends ActionRunner { super(); } - protected async runAction(action: IAction, context: INotificationViewItem): Promise { + protected async runAction(action: IAction, context: INotificationViewItem): Promise { this.telemetryService.publicLog2('workbenchActionExecuted', { id: action.id, from: 'message' }); // Run and make sure to notify on any error again diff --git a/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts b/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts index 94a5a660b43..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'; @@ -23,10 +23,10 @@ export class NotificationsAlerts extends Disposable { } private registerListeners(): void { - this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); + this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); } - private onDidNotificationChange(e: INotificationChangeEvent): void { + private onDidChangeNotification(e: INotificationChangeEvent): void { if (e.kind === NotificationChangeType.ADD) { // ARIA alert for screen readers @@ -37,7 +37,7 @@ export class NotificationsAlerts extends Disposable { if (e.item.message.original instanceof Error) { console.error(e.item.message.original); } else { - console.error(toErrorMessage(e.item.message.value, true)); + console.error(toErrorMessage(e.item.message.linkedText.toString(), true)); } } } @@ -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.onDidLabelChange(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); } }); @@ -60,13 +60,13 @@ export class NotificationsAlerts extends Disposable { private doTriggerAriaAlert(notifiation: INotificationViewItem): void { let alertText: string; if (notifiation.severity === Severity.Error) { - alertText = localize('alertErrorMessage', "Error: {0}", notifiation.message.value); + alertText = localize('alertErrorMessage', "Error: {0}", notifiation.message.linkedText.toString()); } else if (notifiation.severity === Severity.Warning) { - alertText = localize('alertWarningMessage', "Warning: {0}", notifiation.message.value); + alertText = localize('alertWarningMessage', "Warning: {0}", notifiation.message.linkedText.toString()); } else { - alertText = localize('alertInfoMessage', "Info: {0}", notifiation.message.value); + alertText = localize('alertInfoMessage', "Info: {0}", notifiation.message.linkedText.toString()); } alert(alertText); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 6d27e856dbb..8d245dce1cf 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -5,13 +5,13 @@ 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'; -import { NotificationsCenterVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { NotificationsCenterVisibleContext, INotificationsCenterController } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { addClass, removeClass, isAncestor, Dimension } from 'vs/base/browser/dom'; @@ -24,7 +24,7 @@ import { IAction } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; -export class NotificationsCenter extends Themable { +export class NotificationsCenter extends Themable implements INotificationsCenterController { private static readonly MAX_DIMENSIONS = new Dimension(450, 400); @@ -58,7 +58,7 @@ export class NotificationsCenter extends Themable { } private registerListeners(): void { - this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); + this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); } @@ -100,6 +100,9 @@ export class NotificationsCenter extends Themable { // Theming this.updateStyles(); + // Mark as visible + this.model.notifications.forEach(notification => notification.updateVisibility(true)); + // Context Key this.notificationsCenterVisibleContextKey.set(true); @@ -115,7 +118,7 @@ export class NotificationsCenter extends Themable { clearAllAction.enabled = false; } else { notificationsCenterTitle.textContent = localize('notifications', "Notifications"); - clearAllAction.enabled = true; + clearAllAction.enabled = this.model.notifications.some(notification => !notification.hasProgress); } } @@ -167,25 +170,43 @@ export class NotificationsCenter extends Themable { return keybinding ? keybinding.getLabel() : null; } - private onDidNotificationChange(e: INotificationChangeEvent): void { + private onDidChangeNotification(e: INotificationChangeEvent): void { if (!this._isVisible) { return; // only if visible } - let focusGroup = false; + 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: notificationsList.updateNotificationsList(e.index, 0, [e.item]); + 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: - focusGroup = isAncestor(document.activeElement, notificationsCenterContainer); + focusEditor = isAncestor(document.activeElement, notificationsCenterContainer); notificationsList.updateNotificationsList(e.index, 1); + e.item.updateVisibility(false); break; } @@ -197,7 +218,7 @@ export class NotificationsCenter extends Themable { this.hide(); // Restore focus to editor group if we had focus - if (focusGroup) { + if (focusEditor) { this.editorGroupService.activeGroup.focus(); } } @@ -208,13 +229,16 @@ export class NotificationsCenter extends Themable { return; // already hidden } - const focusGroup = isAncestor(document.activeElement, this.notificationsCenterContainer); + const focusEditor = isAncestor(document.activeElement, this.notificationsCenterContainer); // Hide this._isVisible = false; removeClass(this.notificationsCenterContainer, 'visible'); this.notificationsList.hide(); + // Mark as hidden + this.model.notifications.forEach(notification => notification.updateVisibility(false)); + // Context Key this.notificationsCenterVisibleContextKey.set(false); @@ -222,7 +246,7 @@ export class NotificationsCenter extends Themable { this._onDidChangeVisibility.fire(); // Restore focus to editor group if we had focus - if (focusGroup) { + if (focusEditor) { this.editorGroupService.activeGroup.focus(); } } @@ -284,13 +308,15 @@ export class NotificationsCenter extends Themable { this.hide(); // Close all - while (this.model.notifications.length) { - this.model.notifications[0].close(); + for (const notification of this.model.notifications) { + if (!notification.hasProgress) { + notification.close(); + } } } } -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/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 133bb8ff3ab..e41c8692e57 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -75,6 +75,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl // Show Notifications Cneter CommandsRegistry.registerCommand(SHOW_NOTIFICATIONS_CENTER, () => { + toasts.hide(); center.show(); }); @@ -92,6 +93,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl if (center.isVisible) { center.hide(); } else { + toasts.hide(); center.show(); } }); @@ -105,9 +107,9 @@ export function registerNotificationCommands(center: INotificationsCenterControl mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }, - handler: (accessor, args?: any) => { + handler: (accessor, args?) => { const notification = getNotificationFromContext(accessor.get(IListService), args); - if (notification) { + if (notification && !notification.hasProgress) { notification.close(); } } @@ -119,7 +121,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl weight: KeybindingWeight.WorkbenchContrib, when: NotificationFocusedContext, primary: KeyCode.RightArrow, - handler: (accessor, args?: any) => { + handler: (accessor, args?) => { const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification) { notification.expand(); @@ -133,7 +135,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl weight: KeybindingWeight.WorkbenchContrib, when: NotificationFocusedContext, primary: KeyCode.LeftArrow, - handler: (accessor, args?: any) => { + handler: (accessor, args?) => { const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification) { notification.collapse(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index ae4d2e69aff..8fbc52c5b77 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/notificationsList'; +import { localize } from 'vs/nls'; 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 +22,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 +75,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, @@ -85,6 +88,15 @@ export class NotificationsList extends Themable { horizontalScrolling: false, overrideStyles: { listBackground: NOTIFICATIONS_BACKGROUND + }, + accessibilityProvider: { + getAriaLabel(element: INotificationViewItem): string { + if (!element.source) { + return localize('notificationAriaLabel', "{0}, notification", element.message.raw); + } + + return localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", element.message.raw, element.source); + } } } )); @@ -123,7 +135,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([]); } @@ -181,11 +193,22 @@ export class NotificationsList extends Themable { } // Restore DOM focus if we had focus before - if (listHasDOMFocus) { + if (this.isVisible && listHasDOMFocus) { list.domFocus(); } } + 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 +273,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/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index 97c9af2a622..cf45117e80c 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from 'vs/workbench/common/notifications'; +import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from 'vs/workbench/common/notifications'; import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; @@ -12,11 +12,12 @@ import { localize } from 'vs/nls'; export class NotificationsStatus extends Disposable { private notificationsCenterStatusItem: IStatusbarEntryAccessor | undefined; - private currentNotifications = new Set(); + private newNotificationsCount = 0; private currentStatusMessage: [IStatusMessageViewItem, IDisposable] | undefined; - private isNotificationsCenterVisible: boolean | undefined; + private isNotificationsCenterVisible: boolean = false; + private isNotificationsToastsVisible: boolean = false; constructor( private model: INotificationsModel, @@ -34,44 +35,62 @@ export class NotificationsStatus extends Disposable { } private registerListeners(): void { - this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); - this._register(this.model.onDidStatusMessageChange(e => this.onDidStatusMessageChange(e))); + this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); + this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(e))); } - private onDidNotificationChange(e: INotificationChangeEvent): void { - if (this.isNotificationsCenterVisible) { - return; // no change if notification center is visible - } - - // Notification got Added - if (e.kind === NotificationChangeType.ADD) { - this.currentNotifications.add(e.item); - } - - // Notification got Removed - else if (e.kind === NotificationChangeType.REMOVE) { - this.currentNotifications.delete(e.item); + private onDidChangeNotification(e: INotificationChangeEvent): void { + + // Consider a notification as unread as long as it only + // appeared as toast and not in the notification center + if (!this.isNotificationsCenterVisible) { + if (e.kind === NotificationChangeType.ADD) { + this.newNotificationsCount++; + } else if (e.kind === NotificationChangeType.REMOVE && this.newNotificationsCount > 0) { + this.newNotificationsCount--; + } } + // Update in status bar this.updateNotificationsCenterStatusItem(); } private updateNotificationsCenterStatusItem(): void { + + // Figure out how many notifications have progress only if neither + // toasts are visible nor center is visible. In that case we still + // want to give a hint to the user that something is running. + let notificationsInProgress = 0; + if (!this.isNotificationsCenterVisible && !this.isNotificationsToastsVisible) { + for (const notification of this.model.notifications) { + if (notification.hasProgress) { + notificationsInProgress++; + } + } + } + + // Show the bell with a dot if there are unread or in-progress notifications const statusProperties: IStatusbarEntry = { - text: this.currentNotifications.size === 0 ? '$(bell)' : `$(bell) ${this.currentNotifications.size}`, + text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-dot)' : '$(bell)'}`, command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER, - tooltip: this.getTooltip(), + tooltip: this.getTooltip(notificationsInProgress), showBeak: this.isNotificationsCenterVisible }; if (!this.notificationsCenterStatusItem) { - this.notificationsCenterStatusItem = this.statusbarService.addEntry(statusProperties, 'status.notifications', localize('status.notifications', "Notifications"), StatusbarAlignment.RIGHT, -Number.MAX_VALUE /* towards the far end of the right hand side */); + this.notificationsCenterStatusItem = this.statusbarService.addEntry( + statusProperties, + 'status.notifications', + localize('status.notifications', "Notifications"), + StatusbarAlignment.RIGHT, + -Number.MAX_VALUE /* towards the far end of the right hand side */ + ); } else { this.notificationsCenterStatusItem.update(statusProperties); } } - private getTooltip(): string { + private getTooltip(notificationsInProgress: number): string { if (this.isNotificationsCenterVisible) { return localize('hideNotifications', "Hide Notifications"); } @@ -80,28 +99,50 @@ export class NotificationsStatus extends Disposable { return localize('zeroNotifications', "No Notifications"); } - if (this.currentNotifications.size === 0) { - return localize('noNotifications', "No New Notifications"); + if (notificationsInProgress === 0) { + if (this.newNotificationsCount === 0) { + return localize('noNotifications', "No New Notifications"); + } + + if (this.newNotificationsCount === 1) { + return localize('oneNotification', "1 New Notification"); + } + + return localize('notifications', "{0} New Notifications", this.newNotificationsCount); } - if (this.currentNotifications.size === 1) { - return localize('oneNotification', "1 New Notification"); + if (this.newNotificationsCount === 0) { + return localize('noNotificationsWithProgress', "No New Notifications ({0} in progress)", notificationsInProgress); } - return localize('notifications', "{0} New Notifications", this.currentNotifications.size); + if (this.newNotificationsCount === 1) { + return localize('oneNotificationWithProgress', "1 New Notification ({0} in progress)", notificationsInProgress); + } + + return localize('notificationsWithProgress', "{0} New Notifications ({0} in progress)", this.newNotificationsCount, notificationsInProgress); } - update(isCenterVisible: boolean): void { + update(isCenterVisible: boolean, isToastsVisible: boolean): void { + let updateNotificationsCenterStatusItem = false; + if (this.isNotificationsCenterVisible !== isCenterVisible) { this.isNotificationsCenterVisible = isCenterVisible; + this.newNotificationsCount = 0; // Showing the notification center resets the unread counter to 0 + updateNotificationsCenterStatusItem = true; + } - // Showing the notification center resets the counter to 0 - this.currentNotifications.clear(); + if (this.isNotificationsToastsVisible !== isToastsVisible) { + this.isNotificationsToastsVisible = isToastsVisible; + updateNotificationsCenterStatusItem = true; + } + + // Update in status bar as needed + if (updateNotificationsCenterStatusItem) { this.updateNotificationsCenterStatusItem(); } } - private onDidStatusMessageChange(e: IStatusMessageChangeEvent): void { + private onDidChangeStatusMessage(e: IStatusMessageChangeEvent): void { const statusItem = e.item; switch (e.kind) { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index ee6bbffc283..3fd8e6d8e11 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ 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 } from 'vs/base/common/event'; +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 } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { NotificationsToastsVisibleContext, INotificationsToastController } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { Severity, NotificationsFilter } from 'vs/platform/notification/common/notification'; @@ -39,7 +39,7 @@ enum ToastVisibility { VISIBLE } -export class NotificationsToasts extends Themable { +export class NotificationsToasts extends Themable implements INotificationsToastController { private static readonly MAX_WIDTH = 450; private static readonly MAX_NOTIFICATIONS = 3; @@ -53,6 +53,12 @@ export class NotificationsToasts extends Themable { return intervals; })(); + private readonly _onDidChangeVisibility = this._register(new Emitter()); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + + private _isVisible = false; + get isVisible(): boolean { return !!this._isVisible; } + private notificationsToastsContainer: HTMLElement | undefined; private workbenchDimensions: Dimension | undefined; private isNotificationsCenterVisible: boolean | undefined; @@ -90,11 +96,11 @@ export class NotificationsToasts extends Themable { this.model.notifications.forEach(notification => this.addToast(notification)); // Update toasts on notification changes - this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); + this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); }); // Filter - this._register(this.model.onDidFilterChange(filter => { + this._register(this.model.onDidChangeFilter(filter => { if (filter === NotificationsFilter.SILENT || filter === NotificationsFilter.ERROR) { this.hide(); } @@ -114,7 +120,7 @@ export class NotificationsToasts extends Themable { ]); } - private onDidNotificationChange(e: INotificationChangeEvent): void { + private onDidChangeNotification(e: INotificationChangeEvent): void { switch (e.kind) { case NotificationChangeType.ADD: return this.addToast(e.item); @@ -125,11 +131,11 @@ export class NotificationsToasts extends Themable { private addToast(item: INotificationViewItem): void { if (this.isNotificationsCenterVisible) { - return; // do not show toasts while notification center is visibles + return; // do not show toasts while notification center is visible } if (item.silent) { - return; // do not show toats for silenced notifications + return; // do not show toasts for silenced notifications } // Lazily create toasts containers @@ -173,11 +179,8 @@ export class NotificationsToasts extends Themable { const toast: INotificationToast = { item, list: notificationList, container: notificationToastContainer, toast: notificationToast, toDispose: itemDisposables }; this.mapNotificationToToast.set(item, toast); - itemDisposables.add(toDisposable(() => { - if (this.isVisible(toast) && notificationsToastsContainer) { - notificationsToastsContainer.removeChild(toast.container); - } - })); + // When disposed, remove as visible + itemDisposables.add(toDisposable(() => this.updateToastVisibility(toast, false))); // Make visible notificationList.show(); @@ -193,19 +196,24 @@ export class NotificationsToasts extends Themable { // the height computation takes the content of it into account! this.layoutContainer(maxDimensions.height); - // Update when item height changes due to expansion - itemDisposables.add(item.onDidExpansionChange(() => { + // 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.onDidLabelChange(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; } })); @@ -229,6 +237,15 @@ export class NotificationsToasts extends Themable { removeClass(notificationToast, 'notification-fade-in'); addClass(notificationToast, 'notification-fade-in-done'); })); + + // Mark as visible + item.updateVisibility(true); + + // Events + if (!this._isVisible) { + this._isVisible = true; + this._onDidChangeVisibility.fire(); + } } private purgeNotification(item: INotificationViewItem, notificationToastContainer: HTMLElement, notificationList: NotificationsList, disposables: DisposableStore): void { @@ -280,12 +297,13 @@ export class NotificationsToasts extends Themable { } private removeToast(item: INotificationViewItem): void { + let focusEditor = false; + const notificationToast = this.mapNotificationToToast.get(item); - let focusGroup = false; if (notificationToast) { const toastHasDOMFocus = isAncestor(document.activeElement, notificationToast.container); if (toastHasDOMFocus) { - focusGroup = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor + focusEditor = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor } // Listeners @@ -305,7 +323,7 @@ export class NotificationsToasts extends Themable { this.doHide(); // Move focus back to editor group as needed - if (focusGroup) { + if (focusEditor) { this.editorGroupService.activeGroup.focus(); } } @@ -325,14 +343,20 @@ export class NotificationsToasts extends Themable { // Context Key this.notificationsToastsVisibleContextKey.set(false); + + // Events + if (this._isVisible) { + this._isVisible = false; + this._onDidChangeVisibility.fire(); + } } hide(): void { - const focusGroup = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false; + const focusEditor = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false; this.removeToasts(); - if (focusGroup) { + if (focusEditor) { this.editorGroupService.activeGroup.focus(); } } @@ -441,12 +465,12 @@ export class NotificationsToasts extends Themable { notificationToasts.push(toast); break; case ToastVisibility.HIDDEN: - if (!this.isVisible(toast)) { + if (!this.isToastInDOM(toast)) { notificationToasts.push(toast); } break; case ToastVisibility.VISIBLE: - if (this.isVisible(toast)) { + if (this.isToastInDOM(toast)) { notificationToasts.push(toast); } break; @@ -512,7 +536,7 @@ export class NotificationsToasts extends Themable { // In order to measure the client height, the element cannot have display: none toast.container.style.opacity = '0'; - this.setVisibility(toast, true); + this.updateToastVisibility(toast, true); heightToGive -= toast.container.offsetHeight; @@ -524,7 +548,7 @@ export class NotificationsToasts extends Themable { } // Hide or show toast based on context - this.setVisibility(toast, makeVisible); + this.updateToastVisibility(toast, makeVisible); toast.container.style.opacity = ''; if (makeVisible) { @@ -533,20 +557,24 @@ export class NotificationsToasts extends Themable { }); } - private setVisibility(toast: INotificationToast, visible: boolean): void { - if (this.isVisible(toast) === visible) { + private updateToastVisibility(toast: INotificationToast, visible: boolean): void { + if (this.isToastInDOM(toast) === visible) { return; } + // Update visibility in DOM const notificationsToastsContainer = assertIsDefined(this.notificationsToastsContainer); if (visible) { notificationsToastsContainer.appendChild(toast.container); } else { notificationsToastsContainer.removeChild(toast.container); } + + // Update visibility in model + toast.item.updateVisibility(visible); } - private isVisible(toast: INotificationToast): boolean { + private isToastInDOM(toast: INotificationToast): boolean { return !!toast.container.parentElement; } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index a41db2f2ee1..873f43d437c 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; -import { clearNode, addClass, removeClass, toggleClass, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; +import { clearNode, addClass, removeClass, toggleClass, addDisposableListener, EventType, EventHelper, $ } from 'vs/base/browser/dom'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -17,7 +17,7 @@ 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'; @@ -45,14 +45,13 @@ export class NotificationsListDelegate implements IListVirtualDelegate actionHandler.callback(link.href))); + actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, e => { + EventHelper.stop(e, true); + actionHandler.callback(node.href); + })); } messageContainer.appendChild(anchor); - - index = link.offset + link.length; - } - - // Add text after links if any - const textAfter = message.value.substring(index); - if (textAfter) { - messageContainer.appendChild(document.createTextNode(textAfter)); } } @@ -319,7 +307,7 @@ export class NotificationTemplateRenderer extends Disposable { // Container toggleClass(this.template.container, 'expanded', notification.expanded); this.inputDisposables.add(addDisposableListener(this.template.container, EventType.MOUSE_UP, e => { - if (e.button === 1 /* Middle Button */) { + if (!notification.hasProgress && e.button === 1 /* Middle Button */) { EventHelper.stop(e); notification.close(); @@ -344,16 +332,19 @@ export class NotificationTemplateRenderer extends Disposable { // Progress this.renderProgress(notification); - // Label Change Events - this.inputDisposables.add(notification.onDidLabelChange(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; } @@ -416,8 +407,10 @@ export class NotificationTemplateRenderer extends Disposable { actions.push(notification.expanded ? NotificationTemplateRenderer.collapseNotificationAction : NotificationTemplateRenderer.expandNotificationAction); } - // Close - actions.push(NotificationTemplateRenderer.closeNotificationAction); + // Close (unless progress is showing) + if (!notification.hasProgress) { + actions.push(NotificationTemplateRenderer.closeNotificationAction); + } this.template.toolbar.clear(); this.template.toolbar.context = notification; @@ -466,7 +459,7 @@ export class NotificationTemplateRenderer extends Disposable { private renderProgress(notification: INotificationViewItem): void { // Return early if the item has no progress - if (!notification.hasProgress()) { + if (!notification.hasProgress) { this.template.progress.stop().hide(); return; diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index abca35a41a7..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; @@ -62,16 +62,20 @@ } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar { +.monaco-workbench .part.panel > .composite.title > .composite-bar-excess { + width: 100px; +} + +.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; @@ -81,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 8c3117a0b12..1501c87bcce 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -17,7 +17,7 @@ import { ActivityAction, ToggleCompositePinnedAction, ICompositeBar } from 'vs/w import { IActivity } from 'vs/workbench/common/activity'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class ClosePanelAction extends Action { @@ -32,9 +32,8 @@ export class ClosePanelAction extends Action { super(id, name, 'codicon-close'); } - run(): Promise { + async run(): Promise { this.layoutService.setPanelHidden(true); - return Promise.resolve(); } } @@ -51,9 +50,8 @@ export class TogglePanelAction extends Action { super(id, name, layoutService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel'); } - run(): Promise { + async run(): Promise { this.layoutService.setPanelHidden(this.layoutService.isVisible(Parts.PANEL_PART)); - return Promise.resolve(); } } @@ -71,12 +69,11 @@ class FocusPanelAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { // Show panel if (!this.layoutService.isVisible(Parts.PANEL_PART)) { this.layoutService.setPanelHidden(false); - return Promise.resolve(); } // Focus into active panel @@ -84,8 +81,6 @@ class FocusPanelAction extends Action { if (panel) { panel.focus(); } - - return Promise.resolve(); } } @@ -115,13 +110,12 @@ export class ToggleMaximizedPanelAction extends Action { })); } - run(): Promise { + async run(): Promise { if (!this.layoutService.isVisible(Parts.PANEL_PART)) { this.layoutService.setPanelHidden(false); } this.layoutService.toggleMaximizedPanel(); - return Promise.resolve(); } } @@ -133,7 +127,7 @@ const PositionPanelActionId = { interface PanelActionConfig { id: string; - when: ContextKeyExpr; + when: ContextKeyExpression; alias: string; label: string; value: T; @@ -166,10 +160,9 @@ export class SetPanelPositionAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const position = positionByActionId.get(this.id); this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position); - return Promise.resolve(); } } @@ -182,7 +175,7 @@ export class PanelActivityAction extends ActivityAction { super(activity); } - async run(event: any): Promise { + async run(): Promise { await this.panelService.openPanel(this.activity.id, true); this.activate(); } @@ -224,7 +217,7 @@ export class SwitchPanelViewAction extends Action { super(id, name); } - async run(offset: number): Promise { + async run(offset: number): Promise { const pinnedPanels = this.panelService.getPinnedPanels(); const activePanel = this.panelService.getActivePanel(); if (!activePanel) { @@ -256,7 +249,7 @@ export class PreviousPanelViewAction extends SwitchPanelViewAction { super(id, name, panelService); } - run(): Promise { + run(): Promise { return super.run(-1); } } @@ -274,7 +267,7 @@ export class NextPanelViewAction extends SwitchPanelViewAction { super(id, name, panelService); } - run(): Promise { + run(): Promise { return super.run(1); } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index b1cdfc6f4eb..f015ec244ac 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -19,10 +19,10 @@ 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 } from 'vs/workbench/browser/parts/compositeBar'; +import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -33,9 +33,12 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views'; 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; @@ -56,7 +59,7 @@ export class PanelPart extends CompositePart implements IPanelService { //#region IView - readonly minimumWidth: number = 420; + readonly minimumWidth: number = 300; readonly maximumWidth: number = Number.POSITIVE_INFINITY; readonly minimumHeight: number = 77; readonly maximumHeight: number = Number.POSITIVE_INFINITY; @@ -76,18 +79,18 @@ export class PanelPart extends CompositePart implements IPanelService { //#endregion get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean; }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); } - readonly onDidPanelClose: Event = this.onDidCompositeClose.event; + readonly onDidPanelClose = this.onDidCompositeClose.event; private activePanelContextKey: IContextKey; private panelFocusContextKey: IContextKey; private compositeBar: CompositeBar; - private compositeActions: Map = new Map(); + private readonly compositeActions = new Map(); private readonly panelDisposables: Map = new Map(); private blockOpeningPanel = false; - private _contentDimension: Dimension | undefined; + private contentDimension: Dimension | undefined; private panelRegistry: PanelRegistry; @@ -103,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, @@ -124,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, @@ -142,9 +147,13 @@ export class PanelPart extends CompositePart implements IPanelService { getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[], getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(), 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, 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), @@ -311,7 +320,7 @@ export class PanelPart extends CompositePart implements IPanelService { const viewContainer = this.getViewContainer(panelDescriptor.id); if (viewContainer?.hideIfEmpty) { const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors.activeViewDescriptors.length === 0) { + if (viewDescriptors.activeViewDescriptors.length === 0 && this.compositeBar.getPinnedComposites().length > 1) { this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding } } @@ -397,7 +406,17 @@ export class PanelPart extends CompositePart implements IPanelService { getPanels(): readonly PanelDescriptor[] { return this.panelRegistry.getPanels() - .sort((v1, v2) => typeof v1.order === 'number' && typeof v2.order === 'number' ? v1.order - v2.order : NaN); + .sort((v1, v2) => { + if (typeof v1.order !== 'number') { + return 1; + } + + if (typeof v2.order !== 'number') { + return -1; + } + + return v1.order - v2.order; + }); } getPinnedPanels(): readonly PanelDescriptor[] { @@ -454,21 +473,21 @@ export class PanelPart extends CompositePart implements IPanelService { } if (this.layoutService.getPanelPosition() === Position.RIGHT) { - this._contentDimension = new Dimension(width - 1, height); // Take into account the 1px border when layouting + this.contentDimension = new Dimension(width - 1, height); // Take into account the 1px border when layouting } else { - this._contentDimension = new Dimension(width, height); + this.contentDimension = new Dimension(width, height); } // Layout contents - super.layout(this._contentDimension.width, this._contentDimension.height); + super.layout(this.contentDimension.width, this.contentDimension.height); // Layout composite bar this.layoutCompositeBar(); } private layoutCompositeBar(): void { - if (this._contentDimension && this.dimension) { - let availableWidth = this._contentDimension.width - 40; // take padding into account + if (this.contentDimension && this.dimension) { + let availableWidth = this.contentDimension.width - 40; // take padding into account if (this.toolBar) { availableWidth = Math.max(PanelPart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth()); // adjust height for global actions showing } @@ -617,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 265dc48427a..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.getResource() : 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 a1e64954456..00000000000 --- a/src/vs/workbench/browser/parts/quickopen/quickopen.ts +++ /dev/null @@ -1,150 +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); - } - - run(event?: any): 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); - - return Promise.resolve(true); - } -} - -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 72ec6b5eb82..c317db10337 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -23,16 +23,17 @@ 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'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; 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 { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd'; export class SidebarPart extends CompositePart implements IViewletService { @@ -76,9 +77,11 @@ export class SidebarPart extends CompositePart implements IViewletServi get onDidViewletOpen(): Event { return Event.map(this.onDidCompositeOpen.event, compositeEvent => compositeEvent.composite); } get onDidViewletClose(): Event { return this.onDidCompositeClose.event as Event; } - private viewletRegistry: ViewletRegistry; - private sideBarFocusContextKey: IContextKey; - private activeViewletContextKey: IContextKey; + private readonly viewletRegistry = Registry.as(ViewletExtensions.Viewlets); + + private readonly sideBarFocusContextKey = SidebarFocusContext.bindTo(this.contextKeyService); + private readonly activeViewletContextKey = ActiveViewletContext.bindTo(this.contextKeyService); + private blockOpeningViewlet = false; constructor( @@ -90,7 +93,7 @@ export class SidebarPart extends CompositePart implements IViewletServi @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IContextKeyService contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IExtensionService private readonly extensionService: IExtensionService ) { super( @@ -112,11 +115,6 @@ export class SidebarPart extends CompositePart implements IViewletServi { hasTitle: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 } ); - this.viewletRegistry = Registry.as(ViewletExtensions.Viewlets); - - this.sideBarFocusContextKey = SidebarFocusContext.bindTo(contextKeyService); - this.activeViewletContextKey = ActiveViewletContext.bindTo(contextKeyService); - this.registerListeners(); } @@ -163,6 +161,19 @@ export class SidebarPart extends CompositePart implements IViewletServi this.onTitleAreaContextMenu(new StandardMouseEvent(e)); })); + this.titleLabelElement!.draggable = true; + + 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() }; + } + }; + + this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.titleLabelElement!, draggedItemProvider, {})); return titleArea; } @@ -183,6 +194,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 { @@ -302,11 +314,12 @@ class FocusSideBarAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { // Show side bar if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { - return Promise.resolve(this.layoutService.setSideBarHidden(false)); + this.layoutService.setSideBarHidden(false); + return; } // Focus into active viewlet @@ -314,8 +327,6 @@ class FocusSideBarAction extends Action { if (viewlet) { viewlet.focus(); } - - return Promise.resolve(true); } } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 9ec20f5c9c0..6baa583669b 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 { Emitter } 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; } @@ -64,7 +65,7 @@ class StatusbarViewModel extends Disposable { private readonly _onDidChangeEntryVisibility = this._register(new Emitter<{ id: string, visible: boolean }>()); readonly onDidChangeEntryVisibility = this._onDidChangeEntryVisibility.event; - constructor(private storageService: IStorageService) { + constructor(private readonly storageService: IStorageService) { super(); this.restoreState(); @@ -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); } @@ -303,14 +304,12 @@ class ToggleStatusbarEntryVisibilityAction extends Action { this.checked = !model.isHidden(id); } - run(): Promise { + async run(): Promise { if (this.model.isHidden(this.id)) { this.model.show(this.id); } else { this.model.hide(this.id); } - - return Promise.resolve(true); } } @@ -320,10 +319,8 @@ class HideStatusbarEntryAction extends Action { super(id, nls.localize('hide', "Hide '{0}'", name), undefined, true); } - run(): Promise { + async run(): Promise { this.model.hide(this.id); - - return Promise.resolve(true); } } @@ -344,25 +341,25 @@ export class StatusbarPart extends Part implements IStatusbarService { private pendingEntries: IPendingStatusbarEntry[] = []; - private readonly viewModel: StatusbarViewModel; + private readonly viewModel = this._register(new StatusbarViewModel(this.storageService)); + + readonly onDidChangeEntryVisibility = this.viewModel.onDidChangeEntryVisibility; private leftItemsContainer: HTMLElement | undefined; private rightItemsContainer: HTMLElement | undefined; - readonly onDidChangeEntryVisibility: Event<{ id: string, visible: boolean }>; - constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IStorageService storageService: IStorageService, + @IStorageService private readonly 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); - this.viewModel = this._register(new StatusbarViewModel(storageService)); - this.onDidChangeEntryVisibility = this.viewModel.onDidChangeEntryVisibility; + storageKeysSyncRegistryService.registerStorageKey({ key: StatusbarViewModel.HIDDEN_ENTRIES_KEY, version: 1 }); this.registerListeners(); } @@ -640,6 +637,7 @@ export class StatusbarPart extends Part implements IStatusbarService { } class StatusbarEntryItem extends Disposable { + private entry!: IStatusbarEntry; private labelContainer!: HTMLElement; @@ -683,6 +681,7 @@ class StatusbarEntryItem extends Disposable { // Update: Text if (!this.entry || entry.text !== this.entry.text) { this.label.text = entry.text; + this.container.setAttribute('aria-label', entry.text); if (entry.text) { show(this.labelContainer); @@ -706,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 { @@ -742,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' }); @@ -770,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) { @@ -808,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..e7fdf3898d5 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,23 +26,14 @@ 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'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; 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 { @@ -239,7 +230,7 @@ export abstract class MenubarControl extends Disposable { }); }); - return assign(ret, { uri: uri }); + return Object.assign(ret, { uri }); } private notifyUserOfCustomMenubarAccessibility(): void { @@ -293,9 +284,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 +312,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 +426,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 +508,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 +538,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 +609,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 +656,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 +700,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..9b1c6a7b7f3 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,17 +41,12 @@ 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]"); private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]"); private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]"); private static readonly TITLE_DIRTY = '\u25cf '; - private static readonly TITLE_SEPARATOR = isMacintosh ? ' — ' : ' - '; // macOS uses special - separator //#region IView @@ -68,15 +62,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 +75,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 +94,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,8 +124,8 @@ export class TitlebarPart extends Part implements ITitleService { this.updateStyles(); } - private onConfigurationChanged(event: IConfigurationChangeEvent): void { - if (event.affectsConfiguration('window.title')) { + protected onConfigurationChanged(event: IConfigurationChangeEvent): void { + if (event.affectsConfiguration('window.title') || event.affectsConfiguration('window.titleSeparator')) { this.titleUpdater.schedule(); } @@ -150,41 +138,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 @@ -240,6 +203,9 @@ export class TitlebarPart extends Part implements ITitleService { title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.productService.nameLong}`; } + // Replace non-space whitespace + title = title.replace(/[^\S ]/g, ' '); + return title; } @@ -319,7 +285,7 @@ export class TitlebarPart extends Part implements ITitleService { const dirty = editor?.isDirty() && !editor.isSaving() ? TitlebarPart.TITLE_DIRTY : ''; const appName = this.productService.nameLong; const remoteName = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority); - const separator = TitlebarPart.TITLE_SEPARATOR; + const separator = this.configurationService.getValue('window.titleSeparator'); const titleTemplate = this.configurationService.getValue('window.title'); return template(titleTemplate, { @@ -352,7 +318,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 +333,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 +365,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 +385,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 +418,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 +436,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 +457,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 +468,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 +494,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 +513,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 2e4de06c27b..08f031bded2 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), @@ -71,26 +49,38 @@ display: none; } -.monaco-workbench .pane > .pane-body > .empty-view { +.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(.empty) > .empty-view, -.monaco-workbench .pane > .pane-body.empty > :not(.empty-view) { +.monaco-workbench .pane > .pane-body:not(.welcome) > .welcome-view, +.monaco-workbench .pane > .pane-body.welcome > :not(.welcome-view) { display: none; } -.monaco-workbench .pane > .pane-body > .empty-view .monaco-button { +.monaco-workbench .pane > .pane-body > .welcome-view .monaco-button { max-width: 260px; margin-left: auto; margin-right: auto; 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 > * { + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; +} + .customview-tree .monaco-list-row .monaco-tl-contents.align-icon-with-twisty::before { display: none; } @@ -141,6 +131,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 +161,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 32ccfff21ee..37069975595 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'; @@ -43,14 +42,14 @@ import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; 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, @@ -59,14 +58,16 @@ export class CustomTreeViewPane extends ViewPane { @IInstantiationService instantiationService: IInstantiationService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + 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(); } @@ -78,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(); } @@ -118,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; @@ -134,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; @@ -155,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 **/ @@ -188,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(); } @@ -212,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; @@ -237,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 { @@ -269,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 { @@ -298,9 +361,6 @@ export class CustomTreeView extends Disposable implements ITreeView { } this.isVisible = isVisible; - if (this.isVisible) { - this.activate(); - } if (this.tree) { if (this.isVisible) { @@ -354,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 : ''; @@ -402,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 = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); + this._register(treeNavigator); + this._register(treeNavigator.onDidOpenResource(e => { if (!e.browserEvent) { return; } @@ -455,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) { @@ -549,7 +609,6 @@ export class CustomTreeView extends Disposable implements ITreeView { return tree.expand(element, false); })); } - return Promise.resolve(undefined); } setSelection(items: ITreeItem[]): void { @@ -565,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); } } @@ -611,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; @@ -640,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 []; } } @@ -695,7 +742,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer | undefined; - constructor(private themeService: IWorkbenchThemeService) { + constructor(private themeService: IThemeService) { super(); } @@ -852,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; } @@ -879,7 +926,7 @@ class MultipleSelectionActionRunner extends ActionRunner { })); } - runAction(action: IAction, context: TreeViewItemHandleArg): Promise { + runAction(action: IAction, context: TreeViewItemHandleArg): Promise { const selection = this.getSelectedResources(); let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; let actionInSelected: boolean = false; @@ -936,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 7c37ed3075a..eba1b2e980e 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -6,26 +6,25 @@ 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, IActionRunner, ActionRunner } from 'vs/base/common/actions'; -import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction } from 'vs/base/common/actions'; +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 } from 'vs/workbench/common/views'; +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'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -38,28 +37,122 @@ import { Component } from 'vs/workbench/common/component'; import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; -import { parseLinkedText } from 'vs/base/browser/linkedText'; +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 { 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 { - actionRunner?: IActionRunner; id: string; - title: string; showActionsAlways?: boolean; titleMenuId?: MenuId; } +type WelcomeActionClassification = { + viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); +interface IItem { + readonly descriptor: IViewContentDescriptor; + visible: boolean; +} + +class ViewWelcomeController { + + private _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private defaultItem: IItem | undefined; + private items: IItem[] = []; + get contents(): IViewContentDescriptor[] { + const visibleItems = this.items.filter(v => v.visible); + + if (visibleItems.length === 0 && this.defaultItem) { + return [this.defaultItem.descriptor]; + } + + return visibleItems.map(v => v.descriptor); + } + + private contextKeyService: IContextKeyService; + private disposables = new DisposableStore(); + + constructor( + private id: string, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + this.contextKeyService = contextKeyService.createScoped(); + this.disposables.add(this.contextKeyService); + + contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables); + Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables); + this.onDidChangeViewWelcomeContent(); + } + + private onDidChangeViewWelcomeContent(): void { + const descriptors = viewsRegistry.getViewWelcomeContent(this.id); + + this.items = []; + + for (const descriptor of descriptors) { + if (descriptor.when === 'default') { + this.defaultItem = { descriptor, visible: true }; + } else { + const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true; + this.items.push({ descriptor, visible }); + } + } + + this._onDidChange.fire(); + } + + private onDidChangeContext(): void { + let didChange = false; + + for (const item of this.items) { + if (!item.descriptor.when || item.descriptor.when === 'default') { + continue; + } + + const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when); + + if (item.visible === visible) { + continue; + } + + item.visible = visible; + didChange = true; + } + + if (didChange) { + this._onDidChange.fire(); + } + } + + dispose(): void { + this.disposables.dispose(); + } +} + export abstract class ViewPane extends Pane implements IView { private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; @@ -76,8 +169,8 @@ export abstract class ViewPane extends Pane implements IView { protected _onDidChangeTitleArea = this._register(new Emitter()); readonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event; - protected _onDidChangeEmptyState = this._register(new Emitter()); - readonly onDidChangeEmptyState: Event = this._onDidChangeEmptyState.event; + protected _onDidChangeViewWelcomeState = this._register(new Emitter()); + readonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event; private focusedViewContextKey: IContextKey; @@ -86,8 +179,9 @@ export abstract class ViewPane extends Pane implements IView { title: string; private readonly menuActions: ViewMenuActions; + private progressBar!: ProgressBar; + private progressIndicator!: IProgressIndicator; - protected actionRunner?: IActionRunner; private toolbar?: ToolBar; private readonly showActionsAlways: boolean = false; private headerContainer?: HTMLElement; @@ -95,30 +189,42 @@ export abstract class ViewPane extends Pane implements IView { protected twistiesContainer?: HTMLElement; private bodyContainer!: HTMLElement; - private emptyViewContainer!: HTMLElement; - private emptyViewDisposable: IDisposable = Disposable.None; + private viewWelcomeContainer!: HTMLElement; + private viewWelcomeDisposable: IDisposable = Disposable.None; + private viewWelcomeController: ViewWelcomeController; constructor( options: IViewPaneOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService, + @IContextKeyService protected contextKeyService: IContextKeyService, @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, @IInstantiationService protected instantiationService: IInstantiationService, @IOpenerService protected openerService: IOpenerService, @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; - this.actionRunner = options.actionRunner; this.showActionsAlways = !!options.showActionsAlways; this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext)); this._register(this.menuActions.onDidChangeTitle(() => this.updateActions())); + + 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 { @@ -158,7 +264,10 @@ export abstract class ViewPane extends Pane implements IView { this._onDidFocus.fire(); })); this._register(focusTracker.onDidBlur(() => { - this.focusedViewContextKey.reset(); + if (this.focusedViewContextKey.get() === this.id) { + this.focusedViewContextKey.reset(); + } + this._onDidBlur.fire(); })); } @@ -177,7 +286,6 @@ export abstract class ViewPane extends Pane implements IView { actionViewItemProvider: action => this.getActionViewItem(action), ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), - actionRunner: this.actionRunner }); this._register(this.toolbar); @@ -204,20 +312,44 @@ export abstract class ViewPane extends Pane implements IView { this._onDidChangeTitleArea.fire(); } + private scrollableElement!: DomScrollableElement; + protected renderBody(container: HTMLElement): void { this.bodyContainer = container; - this.emptyViewContainer = append(container, $('.empty-view', { tabIndex: 0 })); - // we should update our empty state whenever - const onEmptyViewContentChange = Event.any( - // the registry changes - Event.map(Event.filter(viewsRegistry.onDidChangeEmptyViewContent, id => id === this.id), () => this.isEmpty()), - // or the view's empty state changes - Event.latch(Event.map(this.onDidChangeEmptyState, () => this.isEmpty())) - ); + 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, + })); - this._register(onEmptyViewContentChange(this.updateEmptyState, this)); - this.updateEmptyState(this.isEmpty()); + append(viewWelcomeContainer, this.scrollableElement.getDomNode()); + + const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState); + this._register(onViewWelcomeChange(this.updateViewWelcome, this)); + this.updateViewWelcome(); + } + + protected layoutBody(height: number, width: number): void { + 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 { @@ -229,7 +361,9 @@ export abstract class ViewPane extends Pane implements IView { } focus(): void { - if (this.element) { + if (this.shouldShowWelcome()) { + this.viewWelcomeContainer.focus(); + } else if (this.element) { this.element.focus(); this._onDidFocus.fire(); } @@ -256,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 { @@ -286,28 +420,32 @@ export abstract class ViewPane extends Pane implements IView { // Subclasses to implement for saving state } - private updateEmptyState(isEmpty: boolean): void { - this.emptyViewDisposable.dispose(); + private updateViewWelcome(): void { + this.viewWelcomeDisposable.dispose(); - if (!isEmpty) { - removeClass(this.bodyContainer, 'empty'); - this.emptyViewContainer.innerHTML = ''; + if (!this.shouldShowWelcome()) { + removeClass(this.bodyContainer, 'welcome'); + this.viewWelcomeContainer.innerHTML = ''; + this.scrollableElement.scanDomNode(); return; } - const contents = viewsRegistry.getEmptyViewContent(this.id); + const contents = this.viewWelcomeController.contents; if (contents.length === 0) { - removeClass(this.bodyContainer, 'empty'); - this.emptyViewContainer.innerHTML = ''; + removeClass(this.bodyContainer, 'welcome'); + this.viewWelcomeContainer.innerHTML = ''; + this.scrollableElement.scanDomNode(); return; } const disposables = new DisposableStore(); - addClass(this.bodyContainer, 'empty'); - this.emptyViewContainer.innerHTML = ''; + addClass(this.bodyContainer, 'welcome'); + this.viewWelcomeContainer.innerHTML = ''; - for (const { content } of contents) { + let buttonIndex = 0; + + for (const { content, preconditions } of contents) { const lines = content.split('\n'); for (let line of lines) { @@ -317,32 +455,56 @@ export abstract class ViewPane extends Pane implements IView { continue; } - const p = append(this.emptyViewContainer, $('p')); const linkedText = parseLinkedText(line); - for (const node of linkedText) { - if (typeof node === 'string') { - append(p, document.createTextNode(node)); - } else if (linkedText.length === 1) { - const button = new Button(p, { title: node.title }); - button.label = node.label; - button.onDidClick(_ => this.openerService.open(node.href), null, disposables); - disposables.add(button); - disposables.add(attachButtonStyler(button, this.themeService)); - } else { - const link = this.instantiationService.createInstance(Link, node); - append(p, link.el); - disposables.add(link); - disposables.add(attachLinkStyler(link, 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 (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); + } + } + + 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.emptyViewDisposable = disposables; + this.scrollableElement.scanDomNode(); + this.viewWelcomeDisposable = disposables; } - isEmpty(): boolean { + shouldShowWelcome(): boolean { return false; } } @@ -357,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; @@ -371,8 +744,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private didLayout = false; private dimension: Dimension | undefined; - protected actionRunner: IActionRunner | undefined; - private readonly visibleViewsCountFromCache: number | undefined; private readonly visibleViewsStorageId: string; protected readonly viewsModel: PersistentContributableViewsModel; @@ -432,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`; @@ -445,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)); @@ -575,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); } @@ -670,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; @@ -718,9 +1168,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { { id: viewDescriptor.id, title: viewDescriptor.name, - actionRunner: this.getActionRunner(), - expanded: !collapsed, - minimumBodySize: this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel ? 0 : 120 + expanded: !collapsed }); pane.render(); @@ -749,14 +1197,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return panes; } - getActionRunner(): IActionRunner { - if (!this.actionRunner) { - this.actionRunner = new ActionRunner(); - } - - return this.actionRunner; - } - private onDidRemoveViewDescriptors(removed: IViewDescriptorRef[]): void { removed = removed.sort((a, b) => b.index - a.index); const panesToRemove: ViewPane[] = []; @@ -798,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); @@ -805,6 +1246,107 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.paneItems.splice(index, 0, paneItem); assertIsDefined(this.paneview).addPane(pane, size, index); + + let overlay: ViewPaneDropOverlay | undefined; + + 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) { + + 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.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.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; + } + })); } removePanes(panes: ViewPane[]): void { @@ -883,7 +1425,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } if (!this.areExtensionsReady) { if (this.visibleViewsCountFromCache === undefined) { - return false; + // TODO @sbatten fix hack for #91367 + return this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel; } // Check in cache so that view do not jump. See #29609 return this.visibleViewsCountFromCache === 1; diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 97167ee9659..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 }) => { @@ -559,39 +629,34 @@ export class ViewsService extends Disposable implements IViewsService { } }); } - run(accessor: ServicesAccessor): any { + run(accessor: ServicesAccessor): void { accessor.get(IViewsService).openView(viewDescriptor.id, true); } })); - 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): any { - accessor.get(IViewDescriptorService).moveViewToLocation(viewDescriptor, newLocation); - accessor.get(IViewsService).openView(viewDescriptor.id); + run(accessor: ServicesAccessor): void { + 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 2ea6bcae00f..896fda4970a 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -68,7 +68,7 @@ export abstract class Viewlet extends PaneComposite implements IViewlet { */ export class ViewletDescriptor extends CompositeDescriptor { - public static create( + static create( ctor: { new(...services: Services): Viewlet }, id: string, name: string, @@ -165,17 +165,16 @@ export class ShowViewletAction extends Action { this.enabled = !!this.viewletService && !!this.editorGroupService; } - run(): Promise { + async run(): Promise { // Pass focus to viewlet if not open or focused if (this.otherViewletShowing() || !this.sidebarHasFocus()) { - return this.viewletService.openViewlet(this.viewletId, true); + await this.viewletService.openViewlet(this.viewletId, true); + return; } // Otherwise pass focus to editor group this.editorGroupService.activeGroup.focus(); - - return Promise.resolve(true); } private otherViewletShowing(): boolean { @@ -195,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 11df61ac725..59785385c2a 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'; @@ -41,7 +40,6 @@ import { joinPath } from 'vs/base/common/resources'; import { BrowserStorageService } from 'vs/platform/storage/browser/storageService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; -import { InMemoryFileSystemProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { FileLogService } from 'vs/platform/log/common/fileLogService'; @@ -51,6 +49,8 @@ import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLo import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; 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 }); @@ -175,7 +179,7 @@ class BrowserMain extends Disposable { serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); // Signing - const signService = new SignService(environmentService.configuration.connectionToken); + const signService = new SignService(environmentService.options.connectionToken || this.getCookieValue('vscode-tkn')); serviceCollection.set(ISignService, signService); // Remote Agent @@ -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' }; @@ -322,6 +327,12 @@ class BrowserMain extends Disposable { return undefined; } + + private getCookieValue(name: string): string | undefined { + const match = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531 + + return match ? match.pop() : undefined; + } } export function main(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index acb6715b8d3..30539e9b038 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."), @@ -42,6 +52,19 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio key: 'tabDescription' }, "Controls the format of the label for an editor."), }, + 'workbench.editor.untitled.labelFormat': { + 'type': 'string', + 'enum': ['content', 'name'], + 'enumDescriptions': [ + nls.localize('workbench.editor.untitled.labelFormat.content', "The name of the untitled file is derived from the contents of its first line unless it has an associated file path. It will fallback to the name in case the line is empty or contains no word characters."), + nls.localize('workbench.editor.untitled.labelFormat.name', "The name of the untitled file is not derived from the contents of the file."), + ], + 'default': 'content', + 'description': nls.localize({ + comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], + key: 'untitledLabelFormat' + }, "Controls the format of the label for an untitled editor."), + }, 'workbench.editor.tabCloseButton': { 'type': 'string', 'enum': ['left', 'right', 'off'], @@ -75,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': { @@ -208,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'], @@ -283,6 +301,11 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio })(), 'markdownDescription': windowTitleDescription }, + 'window.titleSeparator': { + 'type': 'string', + 'default': isMacintosh ? ' — ' : ' - ', + 'markdownDescription': nls.localize("window.titleSeparator", "Separator used by `window.title`.") + }, 'window.menuBarVisibility': { 'type': 'string', 'enum': ['default', 'visible', 'toggle', 'hidden', 'compact'], diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 604e4d1e914..32e0aeb38e0 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -6,7 +6,7 @@ import 'vs/workbench/browser/style'; import { localize } from 'vs/nls'; -import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; +import { Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { addClasses, addClass, removeClasses } from 'vs/base/browser/dom'; import { runWhenIdle } from 'vs/base/common/async'; import { getZoomLevel, isFirefox, isSafari, isChrome } from 'vs/base/browser/browser'; @@ -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'; @@ -50,13 +49,13 @@ import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/brows export class Workbench extends Layout { private readonly _onBeforeShutdown = this._register(new Emitter()); - readonly onBeforeShutdown: Event = this._onBeforeShutdown.event; + readonly onBeforeShutdown = this._onBeforeShutdown.event; private readonly _onWillShutdown = this._register(new Emitter()); - readonly onWillShutdown: Event = this._onWillShutdown.event; + readonly onWillShutdown = this._onWillShutdown.event; private readonly _onShutdown = this._register(new Emitter()); - readonly onShutdown: Event = this._onShutdown.event; + readonly onShutdown = this._onShutdown.event; constructor( parent: HTMLElement, @@ -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([ @@ -351,7 +350,7 @@ export class Workbench extends Layout { { id: Parts.SIDEBAR_PART, role: 'complementary', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } }, { id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', positionToString(this.state.panel.position)] }, - { id: Parts.STATUSBAR_PART, role: 'contentinfo', classes: ['statusbar'] } + { id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] } ].forEach(({ id, role, classes, options }) => { const partContainer = this.createPart(id, role, classes); @@ -370,6 +369,9 @@ export class Workbench extends Layout { addClasses(part, 'part', ...classes); part.id = id; part.setAttribute('role', role); + if (role === 'status') { + part.setAttribute('aria-live', 'off'); + } return part; } @@ -384,10 +386,14 @@ export class Workbench extends Layout { // Visibility this._register(notificationsCenter.onDidChangeVisibility(() => { - notificationsStatus.update(notificationsCenter.isVisible); + notificationsStatus.update(notificationsCenter.isVisible, notificationsToasts.isVisible); notificationsToasts.update(notificationsCenter.isVisible); })); + this._register(notificationsToasts.onDidChangeVisibility(() => { + notificationsStatus.update(notificationsCenter.isVisible, notificationsToasts.isVisible); + })); + // Register Commands registerNotificationCommands(notificationsCenter, notificationsToasts); } @@ -410,7 +416,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 46112bcce7c..25080ac61a0 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export const Extensions = { WorkbenchActions: 'workbench.contributions.actions' @@ -22,17 +22,18 @@ 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; } Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionRegistry { - registerWorkbenchAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable { + registerWorkbenchAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpression): IDisposable { return this.registerWorkbenchCommandFromAction(descriptor, alias, category, when); } - private registerWorkbenchCommandFromAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable { + private registerWorkbenchCommandFromAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpression): IDisposable { const registrations = new DisposableStore(); // command @@ -44,7 +45,10 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR KeybindingsRegistry.registerKeybindingRule({ id: descriptor.id, weight: weight, - when: (descriptor.keybindingContext || when ? ContextKeyExpr.and(descriptor.keybindingContext, when) : null), + when: + descriptor.keybindingContext && when + ? ContextKeyExpr.and(descriptor.keybindingContext, when) + : descriptor.keybindingContext || when || null, primary: keybindings ? keybindings.primary : 0, secondary: keybindings?.secondary, win: keybindings?.win, @@ -95,7 +99,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR }; } - private async triggerAndDisposeAction(instantiationService: IInstantiationService, lifecycleService: ILifecycleService, descriptor: SyncActionDescriptor, args: any): Promise { + private async triggerAndDisposeAction(instantiationService: IInstantiationService, lifecycleService: ILifecycleService, descriptor: SyncActionDescriptor, args: unknown): Promise { // run action when workbench is created await lifecycleService.when(LifecyclePhase.Ready); @@ -112,7 +116,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR // otherwise run and dispose try { - const from = args?.from || 'keybinding'; + const from = (args as any)?.from || 'keybinding'; await actionInstance.run(undefined, { from }); } finally { actionInstance.dispose(); diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index 4e4f68ab85e..cbe3c7d61de 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ 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 { + private readonly memento: Memento; constructor( @@ -42,4 +42,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 4c6d4476740..9625606b070 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -38,15 +38,17 @@ export interface IWorkbenchContributionsRegistry { } class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { + private instantiationService: IInstantiationService | undefined; private lifecycleService: ILifecycleService | undefined; - private readonly toBeInstantiated: Map[]> = new Map[]>(); + private readonly toBeInstantiated = new Map[]>(); + + registerWorkbenchContribution(ctor: IConstructorSignature0, phase: LifecyclePhase = LifecyclePhase.Starting): void { - registerWorkbenchContribution(ctor: new (...services: Services) => IWorkbenchContribution, 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 @@ -118,7 +120,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry try { instantiationService.createInstance(ctor); } catch (error) { - console.error(`Unable to instantiate workbench contribution ${(ctor as any).name}.`, error); + console.error(`Unable to instantiate workbench contribution ${ctor.name}.`, error); } } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 9753602c136..9c6a4f57a92 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -5,26 +5,24 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -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, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +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 } 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'; @@ -38,7 +36,7 @@ export const EditorsVisibleContext = new RawContextKey('editorIsOpen', export const EditorPinnedContext = new RawContextKey('editorPinned', false); export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); export const EditorGroupEditorsCountContext = new RawContextKey('groupEditorsCount', 0); -export const NoEditorsVisibleContext: ContextKeyExpr = EditorsVisibleContext.toNegated(); +export const NoEditorsVisibleContext = EditorsVisibleContext.toNegated(); export const TextCompareEditorVisibleContext = new RawContextKey('textCompareEditorVisible', false); export const TextCompareEditorActiveContext = new RawContextKey('textCompareEditorActive', false); export const ActiveEditorGroupEmptyContext = new RawContextKey('activeEditorGroupEmpty', false); @@ -61,22 +59,25 @@ 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; + readonly input: IEditorInput | undefined; /** - * The assigned options of this editor. + * The assigned options of the editor. */ - options: IEditorOptions | undefined; + readonly options: EditorOptions | undefined; /** * The assigned group this editor is showing in. */ - group: IEditorGroup | undefined; + readonly group: IEditorGroup | undefined; /** * The minimum width of this editor. @@ -104,7 +105,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 +117,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 +141,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 +158,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: any): 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 +240,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 +266,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 +279,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, @@ -345,6 +350,11 @@ export interface IRevertOptions { readonly soft?: boolean; } +export interface IMoveResult { + editor: EditorInput | IResourceEditorInputType; + options?: IEditorOptions; +} + export interface IEditorInput extends IDisposable { /** @@ -363,9 +373,13 @@ export interface IEditorInput extends IDisposable { readonly onDidChangeLabel: Event; /** - * Returns the associated resource of this input. + * Returns the optional associated resource of this input. + * + * This resource should be unique for all editors of the same + * kind and is often used to identify the editor input among + * others. */ - getResource(): URI | undefined; + readonly resource: URI | undefined; /** * Unique type identifier for this inpput. @@ -440,7 +454,17 @@ 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 + * the editors resource (or is a child of). + * + * Implementors are free to not implement this method to signal no intent + * to participate. If an editor is returned though, it will replace the + * current one with that editor and optional options. + */ + move(group: GroupIdentifier, target: URI): IMoveResult | undefined; /** * Returns if the other object matches this input. @@ -470,11 +494,9 @@ export abstract class EditorInput extends Disposable implements IEditorInput { private disposed: boolean = false; - abstract getTypeId(): string; + abstract get resource(): URI | undefined; - getResource(): URI | undefined { - return undefined; - } + abstract getTypeId(): string; getName(): string { return `Editor ${this.getTypeId()}`; @@ -540,8 +562,10 @@ 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; } /** @@ -574,7 +598,7 @@ export abstract class TextResourceEditorInput extends EditorInput { private static readonly MEMOIZER = createMemoizer(); constructor( - protected readonly resource: URI, + public readonly resource: URI, @IEditorService protected readonly editorService: IEditorService, @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, @ITextFileService protected readonly textFileService: ITextFileService, @@ -584,13 +608,26 @@ export abstract class TextResourceEditorInput extends EditorInput { ) { super(); - // 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.registerListeners(); } - getResource(): URI { - return this.resource; + protected registerListeners(): void { + + // Clear label memoizer on certain events that have impact + 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 { @@ -665,13 +702,7 @@ 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 - } - - const model = this.textFileService.files.get(this.resource); - - return model?.isReadonly() || this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); + return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } isSaving(): boolean { @@ -709,14 +740,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); } } @@ -763,7 +794,7 @@ export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeS /** * Gets the resource this editor is about. */ - getResource(): URI; + readonly resource: URI; /** * Sets the preferred encoding to use for this input. @@ -779,6 +810,11 @@ export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeS * Forces this file input to open as binary instead of text. */ setForceOpenAsBinary(): void; + + /** + * Figure out if the input has been resolved or not. + */ + isResolved(): boolean; } /** @@ -799,6 +835,10 @@ export class SideBySideEditorInput extends EditorInput { this.registerListeners(); } + get resource(): URI | undefined { + return undefined; + } + get master(): EditorInput { return this._master; } @@ -847,14 +887,14 @@ 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); } getTelemetryDescriptor(): { [key: string]: unknown } { const descriptor = this.master.getTelemetryDescriptor(); - return assign(descriptor, super.getTelemetryDescriptor()); + return Object.assign(descriptor, super.getTelemetryDescriptor()); } private registerListeners(): void { @@ -1114,7 +1154,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; } @@ -1168,7 +1208,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 @@ -1182,7 +1222,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 @@ -1204,6 +1244,8 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio if (this.selectionRevealType === TextEditorSelectionRevealType.NearTop) { editor.revealRangeNearTop(range, scrollType); + } else if (this.selectionRevealType === TextEditorSelectionRevealType.NearTopIfOutsideViewport) { + editor.revealRangeNearTopIfOutsideViewport(range, scrollType); } else if (this.selectionRevealType === TextEditorSelectionRevealType.CenterIfOutsideViewport) { editor.revealRangeInCenterIfOutsideViewport(range, scrollType); } else { @@ -1264,6 +1306,7 @@ interface IEditorPartConfiguration { highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; tabSizing?: 'fit' | 'shrink'; + titleScrollbarSizing?: 'default' | 'large'; focusRecentEditorAfterClose?: boolean; showIcons?: boolean; enablePreview?: boolean; @@ -1295,7 +1338,8 @@ export interface IEditorPartOptionsChangeEvent { export enum SideBySideEditor { MASTER = 1, - DETAILS = 2 + DETAILS = 2, + BOTH = 3 } export interface IResourceOptions { @@ -1303,26 +1347,38 @@ 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; } - const resource = editor.getResource(); + const resource = editor.resource; if (!resource || !options || !options.filterByScheme) { 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; @@ -1343,11 +1399,14 @@ export interface IEditorMemento { clearEditorState(resource: URI, group?: IEditorGroup): void; clearEditorState(editor: EditorInput, group?: IEditorGroup): void; + + moveEditorState(source: URI, target: URI): void; } 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(); @@ -1367,12 +1426,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 { @@ -1400,7 +1467,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 []; } @@ -1421,7 +1488,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/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 511a03b9376..3ff8760e8e3 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -45,8 +45,8 @@ export interface ISerializedEditorGroup { preview?: number; } -export function isSerializedEditorGroup(obj?: any): obj is ISerializedEditorGroup { - const group: ISerializedEditorGroup = obj; +export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup { + const group = obj as ISerializedEditorGroup; return obj && typeof obj === 'object' && Array.isArray(group.editors) && Array.isArray(group.mru); } 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/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index 056ce813d6c..2627ffc5890 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -141,7 +141,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel /** * Updates the text editor model with the provided value. If the value is the same as the model has, this is a no-op. */ - protected updateTextEditorModel(newValue?: ITextBufferFactory, preferredMode?: string): void { + updateTextEditorModel(newValue?: ITextBufferFactory, preferredMode?: string): void { if (!this.isResolved()) { return; } diff --git a/src/vs/workbench/common/memento.ts b/src/vs/workbench/common/memento.ts index 795b6ac8d90..97b12facfd9 100644 --- a/src/vs/workbench/common/memento.ts +++ b/src/vs/workbench/common/memento.ts @@ -61,6 +61,7 @@ export class Memento { } class ScopedMemento { + private readonly mementoObj: MementoObject; constructor(private id: string, private scope: StorageScope, private storageService: IStorageService) { @@ -87,4 +88,4 @@ class ScopedMemento { this.storageService.remove(this.id, this.scope); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index a6b19e5df2c..cf6840d00c8 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -3,46 +3,64 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter, INotificationProgressProperties } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Action } from 'vs/base/common/actions'; import { isErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { startsWith } from 'vs/base/common/strings'; -import { localize } from 'vs/nls'; import { find, equals } from 'vs/base/common/arrays'; +import { parseLinkedText, LinkedText } from 'vs/base/common/linkedText'; export interface INotificationsModel { - // - // Notifications as Toasts/Center - // + //#region Notifications as Toasts/Center readonly notifications: INotificationViewItem[]; - readonly onDidNotificationChange: Event; - readonly onDidFilterChange: Event; + readonly onDidChangeNotification: Event; + readonly onDidChangeFilter: Event; addNotification(notification: INotification): INotificationHandle; setFilter(filter: NotificationsFilter): void; - // - // Notifications as Status - // + //#endregion + + + //#region Notifications as Status readonly statusMessage: IStatusMessageViewItem | undefined; - readonly onDidStatusMessageChange: Event; + readonly onDidChangeStatusMessage: Event; showStatusMessage(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable; + + //#endregion } 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 { @@ -87,19 +111,30 @@ export interface IStatusMessageChangeEvent { kind: StatusMessageChangeType; } -export class NotificationHandle implements INotificationHandle { +export class NotificationHandle extends Disposable implements INotificationHandle { - private readonly _onDidClose: Emitter = new Emitter(); - readonly onDidClose: Event = this._onDidClose.event; + private readonly _onDidClose = this._register(new Emitter()); + readonly onDidClose = this._onDidClose.event; + + private readonly _onDidChangeVisibility = this._register(new Emitter()); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + + constructor(private readonly item: INotificationViewItem, private readonly onClose: (item: INotificationViewItem) => void) { + super(); - constructor(private readonly item: INotificationViewItem, private readonly closeItem: (item: INotificationViewItem) => void) { this.registerListeners(); } private registerListeners(): void { + + // Visibility + this._register(this.item.onDidChangeVisibility(visible => this._onDidChangeVisibility.fire(visible))); + + // Closing Event.once(this.item.onDidClose)(() => { this._onDidClose.fire(); - this._onDidClose.dispose(); + + this.dispose(); }); } @@ -120,8 +155,9 @@ export class NotificationHandle implements INotificationHandle { } close(): void { - this.closeItem(this.item); - this._onDidClose.dispose(); + this.onClose(this.item); + + this.dispose(); } } @@ -129,14 +165,14 @@ export class NotificationsModel extends Disposable implements INotificationsMode private static readonly NO_OP_NOTIFICATION = new NoOpNotification(); - private readonly _onDidNotificationChange = this._register(new Emitter()); - readonly onDidNotificationChange: Event = this._onDidNotificationChange.event; + private readonly _onDidChangeNotification = this._register(new Emitter()); + readonly onDidChangeNotification = this._onDidChangeNotification.event; - private readonly _onDidStatusMessageChange = this._register(new Emitter()); - readonly onDidStatusMessageChange: Event = this._onDidStatusMessageChange.event; + private readonly _onDidChangeStatusMessage = this._register(new Emitter()); + readonly onDidChangeStatusMessage = this._onDidChangeStatusMessage.event; - private readonly _onDidFilterChange = this._register(new Emitter()); - readonly onDidFilterChange: Event = this._onDidFilterChange.event; + private readonly _onDidChangeFilter = this._register(new Emitter()); + readonly onDidChangeFilter = this._onDidChangeFilter.event; private readonly _notifications: INotificationViewItem[] = []; get notifications(): INotificationViewItem[] { return this._notifications; } @@ -149,7 +185,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode setFilter(filter: NotificationsFilter): void { this.filter = filter; - this._onDidFilterChange.fire(filter); + this._onDidChangeFilter.fire(filter); } addNotification(notification: INotification): INotificationHandle { @@ -168,13 +204,13 @@ export class NotificationsModel extends Disposable implements INotificationsMode this._notifications.splice(0, 0, item); // Events - this._onDidNotificationChange.fire({ item, index: 0, kind: NotificationChangeType.ADD }); + this._onDidChangeNotification.fire({ item, index: 0, kind: NotificationChangeType.ADD }); // Wrap into handle - return new NotificationHandle(item, item => this.closeItem(item)); + return new NotificationHandle(item, item => this.onClose(item)); } - private closeItem(item: INotificationViewItem): void { + private onClose(item: INotificationViewItem): void { const liveItem = this.findNotification(item); if (liveItem && liveItem !== item) { liveItem.close(); // item could have been replaced with another one, make sure to close the live item @@ -194,31 +230,24 @@ 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._onDidNotificationChange.fire({ item, index, kind: NotificationChangeType.CHANGE }); + this._onDidChangeNotification.fire({ item, index, kind, detail }); } }; - const itemExpansionChangeListener = item.onDidExpansionChange(() => onItemChangeEvent()); - - const itemLabelChangeListener = item.onDidLabelChange(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) { this._notifications.splice(index, 1); - this._onDidNotificationChange.fire({ item, index, kind: NotificationChangeType.REMOVE }); + this._onDidChangeNotification.fire({ item, index, kind: NotificationChangeType.REMOVE }); } }); @@ -233,14 +262,14 @@ export class NotificationsModel extends Disposable implements INotificationsMode // Remember as current status message and fire events this._statusMessage = item; - this._onDidStatusMessageChange.fire({ kind: StatusMessageChangeType.ADD, item }); + this._onDidChangeStatusMessage.fire({ kind: StatusMessageChangeType.ADD, item }); return toDisposable(() => { // Only reset status message if the item is still the one we had remembered if (this._statusMessage === item) { this._statusMessage = undefined; - this._onDidStatusMessageChange.fire({ kind: StatusMessageChangeType.REMOVE, item }); + this._onDidChangeStatusMessage.fire({ kind: StatusMessageChangeType.REMOVE, item }); } }); } @@ -257,22 +286,23 @@ export interface INotificationViewItem { readonly expanded: boolean; readonly canCollapse: boolean; + readonly hasProgress: boolean; - readonly onDidExpansionChange: Event; + readonly onDidChangeExpansion: Event; + readonly onDidChangeVisibility: Event; + readonly onDidChangeContent: Event; readonly onDidClose: Event; - readonly onDidLabelChange: Event; expand(): void; collapse(skipEvents?: boolean): void; toggle(): void; - hasProgress(): boolean; - hasPrompt(): boolean; - updateSeverity(severity: Severity): void; updateMessage(message: NotificationMessage): void; updateActions(actions?: INotificationActions): void; + updateVisibility(visible: boolean): void; + close(): void; equals(item: INotificationViewItem): boolean; @@ -282,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 { @@ -309,8 +339,8 @@ export interface INotificationViewItemProgress extends INotificationProgress { export class NotificationViewItemProgress extends Disposable implements INotificationViewItemProgress { private readonly _state: INotificationViewItemProgressState; - private readonly _onDidChange: Emitter = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; constructor() { super(); @@ -388,31 +418,30 @@ export interface IMessageLink { export interface INotificationMessage { raw: string; original: NotificationMessage; - value: string; - links: IMessageLink[]; + linkedText: LinkedText; } export class NotificationViewItem extends Disposable implements INotificationViewItem { private static readonly MAX_MESSAGE_LENGTH = 1000; - // Example link: "Some message with [link text](http://link.href)." - // RegEx: [, anything not ], ], (, http://|https://|command:, no whitespace) - private static readonly LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; - private _expanded: boolean | undefined; + private _visible: boolean = false; private _actions: INotificationActions | undefined; private _progress: NotificationViewItemProgress | undefined; - private readonly _onDidExpansionChange: Emitter = this._register(new Emitter()); - readonly onDidExpansionChange: Event = this._onDidExpansionChange.event; + private readonly _onDidChangeExpansion = this._register(new Emitter()); + readonly onDidChangeExpansion = this._onDidChangeExpansion.event; - private readonly _onDidClose: Emitter = this._register(new Emitter()); - readonly onDidClose: Event = this._onDidClose.event; + private readonly _onDidClose = this._register(new Emitter()); + readonly onDidClose = this._onDidClose.event; - private readonly _onDidLabelChange: Emitter = this._register(new Emitter()); - readonly onDidLabelChange: Event = this._onDidLabelChange.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; static create(notification: INotification, filter: NotificationsFilter = NotificationsFilter.OFF): INotificationViewItem | undefined { if (!notification || !notification.message || isPromiseCanceledError(notification.message)) { @@ -438,7 +467,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie actions = { primary: notification.message.actions }; } - return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, actions); + return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, notification.progress, actions); } private static parseNotificationMessage(input: NotificationMessage): INotificationMessage | undefined { @@ -464,23 +493,9 @@ export class NotificationViewItem extends Disposable implements INotificationVie message = message.replace(/(\r\n|\n|\r)/gm, ' ').trim(); // Parse Links - const links: IMessageLink[] = []; - message.replace(NotificationViewItem.LINK_REGEX, (matchString: string, name: string, href: string, title: string, offset: number) => { - let massagedTitle: string; - if (title && title.length > 0) { - massagedTitle = title; - } else if (startsWith(href, 'command:')) { - massagedTitle = localize('executeCommand', "Click to execute command '{0}'", href.substr('command:'.length)); - } else { - massagedTitle = href; - } + const linkedText = parseLinkedText(message); - links.push({ name, href, title: massagedTitle, offset, length: matchString.length }); - - return matchString; - }); - - return { raw, value: message, links, original: input }; + return { raw, linkedText, original: input }; } private constructor( @@ -489,28 +504,41 @@ export class NotificationViewItem extends Disposable implements INotificationVie private _silent: boolean | undefined, private _message: INotificationMessage, private _source: string | undefined, + progress: INotificationProgressProperties | undefined, actions?: INotificationActions ) { super(); + if (progress) { + this.setProgress(progress); + } + this.setActions(actions); } + private setProgress(progress: INotificationProgressProperties): void { + if (progress.infinite) { + this.progress.infinite(); + } else if (progress.total) { + this.progress.total(progress.total); + + if (progress.worked) { + this.progress.worked(progress.worked); + } + } + } + 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 { @@ -526,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; } @@ -542,7 +570,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return !!this._silent; } - hasPrompt(): boolean { + private get hasActions(): boolean { if (!this._actions) { return false; } @@ -554,14 +582,14 @@ export class NotificationViewItem extends Disposable implements INotificationVie return this._actions.primary.length > 0; } - hasProgress(): boolean { + get hasProgress(): boolean { return !!this._progress; } get progress(): INotificationViewItemProgress { if (!this._progress) { this._progress = this._register(new NotificationViewItemProgress()); - this._register(this._progress.onDidChange(() => this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.PROGRESS }))); + this._register(this._progress.onDidChange(() => this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.PROGRESS }))); } return this._progress; @@ -581,7 +609,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie updateSeverity(severity: Severity): void { this._severity = severity; - this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.SEVERITY }); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.SEVERITY }); } updateMessage(input: NotificationMessage): void { @@ -591,13 +619,20 @@ export class NotificationViewItem extends Disposable implements INotificationVie } this._message = message; - this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.MESSAGE }); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.MESSAGE }); } updateActions(actions?: INotificationActions): void { this.setActions(actions); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.ACTIONS }); + } - this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.ACTIONS }); + updateVisibility(visible: boolean): void { + if (this._visible !== visible) { + this._visible = visible; + + this._onDidChangeVisibility.fire(visible); + } } expand(): void { @@ -606,7 +641,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie } this._expanded = true; - this._onDidExpansionChange.fire(); + this._onDidChangeExpansion.fire(); } collapse(skipEvents?: boolean): void { @@ -617,7 +652,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie this._expanded = false; if (!skipEvents) { - this._onDidExpansionChange.fire(); + this._onDidChangeExpansion.fire(); } } @@ -636,7 +671,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie } equals(other: INotificationViewItem): boolean { - if (this.hasProgress() || other.hasProgress()) { + if (this.hasProgress || other.hasProgress) { return false; } @@ -644,7 +679,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return false; } - if (this._message.value !== other.message.value) { + if (this._message.raw !== other.message.raw) { return false; } @@ -656,21 +691,19 @@ export class NotificationViewItem extends Disposable implements INotificationVie export class ChoiceAction extends Action { - private readonly _onDidRun = new Emitter(); - readonly onDidRun: Event = this._onDidRun.event; + private readonly _onDidRun = this._register(new Emitter()); + readonly onDidRun = this._onDidRun.event; 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; @@ -679,12 +712,6 @@ export class ChoiceAction extends Action { get keepOpen(): boolean { return this._keepOpen; } - - dispose(): void { - super.dispose(); - - this._onDidRun.dispose(); - } } class StatusMessageViewItem { diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index c509716fc49..2a7844da48f 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import * as objects from 'vs/base/common/objects'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { basename, extname, relativePath } from 'vs/base/common/resources'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -18,13 +18,17 @@ import { withNullAsUndefined } from 'vs/base/common/types'; export class ResourceContextKey extends Disposable implements IContextKey { + // NOTE: DO NOT CHANGE THE DEFAULT VALUE TO ANYTHING BUT + // UNDEFINED! IT IS IMPORTANT THAT DEFAULTS ARE INHERITED + // FROM THE PARENT CONTEXT AND ONLY UNDEFINED DOES THIS + static readonly Scheme = new RawContextKey('resourceScheme', undefined); static readonly Filename = new RawContextKey('resourceFilename', undefined); static readonly LangId = new RawContextKey('resourceLangId', undefined); static readonly Resource = new RawContextKey('resource', undefined); static readonly Extension = new RawContextKey('resourceExtname', undefined); - static readonly HasResource = new RawContextKey('resourceSet', false); - static readonly IsFileSystemResource = new RawContextKey('isFileSystemResource', false); + static readonly HasResource = new RawContextKey('resourceSet', undefined); + static readonly IsFileSystemResource = new RawContextKey('isFileSystemResource', undefined); private readonly _resourceKey: IContextKey; private readonly _schemeKey: IContextKey; @@ -106,11 +110,11 @@ export class ResourceGlobMatcher extends Disposable { private static readonly NO_ROOT: string | null = null; - private readonly _onExpressionChange: Emitter = this._register(new Emitter()); - readonly onExpressionChange: Event = this._onExpressionChange.event; + 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 1620263f6cb..c303cd3c1ab 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -6,7 +6,7 @@ import { Command } from 'vs/editor/common/modes'; import { UriComponents, URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -17,9 +17,11 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { flatten } from 'vs/base/common/arrays'; +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'; @@ -53,6 +55,7 @@ export interface IViewContainerDescriptor { readonly extensionId?: ExtensionIdentifier; + readonly rejectAddedViews?: boolean; } export interface IViewContainersRegistry { @@ -177,7 +180,7 @@ export interface IViewDescriptor { readonly ctorDescriptor: SyncDescriptor; - readonly when?: ContextKeyExpr; + readonly when?: ContextKeyExpression; readonly order?: number; @@ -211,8 +214,21 @@ export interface IViewDescriptorCollection extends IDisposable { readonly allViewDescriptors: IViewDescriptor[]; } +export enum ViewContentPriority { + Normal = 0, + Low = 1, + Lowest = 2 +} + export interface IViewContentDescriptor { readonly content: string; + readonly when?: ContextKeyExpression | 'default'; + readonly priority?: ViewContentPriority; + + /** + * ordered preconditions for each button in the content + */ + readonly preconditions?: (ContextKeyExpression | undefined)[]; } export interface IViewsRegistry { @@ -235,9 +251,21 @@ export interface IViewsRegistry { getViewContainer(id: string): ViewContainer | null; - readonly onDidChangeEmptyViewContent: Event; - registerEmptyViewContent(id: string, viewContent: IViewContentDescriptor): IDisposable; - getEmptyViewContent(id: string): IViewContentDescriptor[]; + readonly onDidChangeViewWelcomeContent: Event; + registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable; + getViewWelcomeContent(id: string): IViewContentDescriptor[]; +} + +function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewContentDescriptor): number { + const aPriority = a.priority ?? ViewContentPriority.Normal; + const bPriority = b.priority ?? ViewContentPriority.Normal; + + if (aPriority !== bPriority) { + return aPriority - bPriority; + } + + // No priroity, keep views sorted in the order they got registered + return 0; } class ViewsRegistry extends Disposable implements IViewsRegistry { @@ -251,12 +279,12 @@ class ViewsRegistry extends Disposable implements IViewsRegistry { private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event; - private readonly _onDidChangeEmptyViewContent: Emitter = this._register(new Emitter()); - readonly onDidChangeEmptyViewContent: Event = this._onDidChangeEmptyViewContent.event; + private readonly _onDidChangeViewWelcomeContent: Emitter = this._register(new Emitter()); + readonly onDidChangeViewWelcomeContent: Event = this._onDidChangeViewWelcomeContent.event; private _viewContainers: ViewContainer[] = []; private _views: Map = new Map(); - private _emptyViewContents = new SetMap(); + private _viewWelcomeContents = new SetMap(); registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { this.addViews(views, viewContainer); @@ -306,19 +334,20 @@ class ViewsRegistry extends Disposable implements IViewsRegistry { return null; } - registerEmptyViewContent(id: string, viewContent: IViewContentDescriptor): IDisposable { - this._emptyViewContents.add(id, viewContent); - this._onDidChangeEmptyViewContent.fire(id); + registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable { + this._viewWelcomeContents.add(id, viewContent); + this._onDidChangeViewWelcomeContent.fire(id); return toDisposable(() => { - this._emptyViewContents.delete(id, viewContent); - this._onDidChangeEmptyViewContent.fire(id); + this._viewWelcomeContents.delete(id, viewContent); + this._onDidChangeViewWelcomeContent.fire(id); }); } - getEmptyViewContent(id: string): IViewContentDescriptor[] { + getViewWelcomeContent(id: string): IViewContentDescriptor[] { const result: IViewContentDescriptor[] = []; - this._emptyViewContents.forEach(id, descriptor => result.push(descriptor)); + this._viewWelcomeContents.forEach(id, descriptor => result.push(descriptor)); + mergeSort(result, compareViewContentDescriptors); return result; } @@ -330,8 +359,8 @@ class ViewsRegistry extends Disposable implements IViewsRegistry { this._viewContainers.push(viewContainer); } for (const viewDescriptor of viewDescriptors) { - if (views.some(v => v.id === viewDescriptor.id)) { - throw new Error(localize('duplicateId', "A view with id '{0}' is already registered in the container '{1}'", viewDescriptor.id, viewContainer.id)); + if (this.getView(viewDescriptor.id) !== null) { + throw new Error(localize('duplicateId', "A view with id '{0}' is already registered", viewDescriptor.id)); } views.push(viewDescriptor); } @@ -375,6 +404,7 @@ export interface IView { setExpanded(expanded: boolean): boolean; + getProgressIndicator(): IProgressIndicator | undefined; } export interface IViewsViewlet extends IViewlet { @@ -399,6 +429,7 @@ export interface IViewsService { closeView(id: string): void; + getProgressIndicator(id: string): IProgressIndicator | undefined; } /** @@ -461,6 +492,8 @@ export interface ITreeView extends IDisposable { readonly onDidChangeTitle: Event; + readonly onDidChangeWelcomeState: Event; + refresh(treeItems?: ITreeItem[]): Promise; setVisibility(visible: boolean): void; @@ -479,9 +512,6 @@ export interface ITreeView extends IDisposable { setFocus(item: ITreeItem): void; - getPrimaryActions(): IAction[]; - - getSecondaryActions(): IAction[]; } export interface IRevealOptions { @@ -547,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; @@ -575,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 6e1bb165f5e..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(); } @@ -68,7 +71,7 @@ export class BackupRestorer implements IWorkbenchContribution { private findEditorByResource(resource: URI): IEditorInput | undefined { for (const editor of this.editorService.editors) { - if (isEqual(editor.getResource(), resource)) { + if (isEqual(editor.resource, resource)) { return editor; } } @@ -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 94b546b5a2f..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 @@ -12,12 +12,10 @@ import { URI } from 'vs/base/common/uri'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { DefaultEndOfLine } from 'vs/editor/common/model'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-browser/backupTracker'; -import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -33,6 +31,8 @@ import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/ele import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; +import { TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); const backupHome = path.join(userdataDir, 'Backups'); @@ -51,15 +51,8 @@ class TestBackupRestorer extends BackupRestorer { } } -class ServiceAccessor { - constructor( - @ITextFileService public textFileService: TestTextFileService - ) { - } -} - suite('BackupRestorer', () => { - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; let disposables: IDisposable[] = []; @@ -105,7 +98,7 @@ suite('BackupRestorer', () => { const editorService: EditorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); await part.whenRestored; @@ -125,24 +118,36 @@ suite('BackupRestorer', () => { let counter = 0; for (const editor of editorService.editors) { - const resource = editor.getResource(); + 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 3e023ba64ae..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 @@ -10,17 +10,15 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-browser/backupTracker'; -import { TestLifecycleService, TestFilesConfigurationService, TestContextService, TestFileService, TestFileDialogService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; 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,18 +30,21 @@ import { toResource } from 'vs/base/test/common/utils'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILogService } from 'vs/platform/log/common/log'; -import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files'; +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'; import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { TestTextFileService, TestElectronService, workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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 } 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'); @@ -52,23 +53,6 @@ const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); -class ServiceAccessor { - constructor( - @ILifecycleService public lifecycleService: TestLifecycleService, - @ITextFileService public textFileService: TestTextFileService, - @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, - @IWorkspaceContextService public contextService: TestContextService, - @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService, - @IElectronService public electronService: TestElectronService, - @IFileDialogService public fileDialogService: TestFileDialogService, - @IBackupFileService public backupFileService: NodeTestBackupFileService, - @IWorkingCopyService public workingCopyService: IWorkingCopyService, - @IEditorService public editorService: IEditorService - ) { - } -} - class TestBackupTracker extends NativeBackupTracker { constructor( @@ -76,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; @@ -102,12 +86,12 @@ class BeforeShutdownEventImpl implements BeforeShutdownEvent { } suite('BackupTracker', () => { - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; let disposables: IDisposable[] = []; setup(async () => { const instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( @@ -134,11 +118,22 @@ suite('BackupTracker', () => { return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); }); - async function createTracker(): Promise<[ServiceAccessor, EditorPart, BackupTracker, IInstantiationService]> { + async function createTracker(autoSaveEnabled = false): Promise<[TestServiceAccessor, EditorPart, BackupTracker, IInstantiationService]> { const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IBackupFileService, backupFileService); + const configurationService = new TestConfigurationService(); + if (autoSaveEnabled) { + configurationService.setUserConfiguration('files', { autoSave: 'afterDelay', autoSaveDelay: 1 }); + } + instantiationService.stub(IConfigurationService, configurationService); + + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( + instantiationService.createInstance(MockContextKeyService), + configurationService + )); + const part = instantiationService.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -148,7 +143,7 @@ suite('BackupTracker', () => { const editorService: EditorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); await part.whenRestored; @@ -157,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; @@ -170,13 +165,13 @@ suite('BackupTracker', () => { await accessor.backupFileService.joinBackupResource(); - assert.equal(accessor.backupFileService.hasBackupSync(untitledEditor.getResource()), true); + assert.equal(accessor.backupFileService.hasBackupSync(untitledEditor.resource), true); untitledModel.dispose(); await accessor.backupFileService.joinDiscardBackup(); - assert.equal(accessor.backupFileService.hasBackupSync(untitledEditor.getResource()), false); + assert.equal(accessor.backupFileService.hasBackupSync(untitledEditor.resource), false); part.dispose(); tracker.dispose(); @@ -219,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'); @@ -228,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('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'); @@ -248,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'); @@ -255,13 +247,39 @@ suite('BackupTracker', () => { const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - assert.ok(event.value); + + const veto = await event.value; + assert.ok(veto); part.dispose(); 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 if auto save is on', async function () { + const [accessor, part, tracker] = await createTracker(true /* auto save enabled */); + + const resource = toResource.call(this, '/path/index.txt'); + await accessor.editorService.openEditor({ resource, options: { pinned: true } }); + + const model = accessor.textFileService.files.get(resource); + + await model?.load(); + model?.textEditorModel?.setValue('foo'); + assert.equal(accessor.workingCopyService.dirtyCount, 1); + + const event = new BeforeShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + const veto = await event.value; + assert.ok(!veto); + + assert.equal(accessor.workingCopyService.dirtyCount, 0); + + part.dispose(); + tracker.dispose(); + }); + + 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'); @@ -278,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'); @@ -309,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()); @@ -452,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/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 7d4e04a3136..faf9b538534 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -64,9 +64,9 @@ class UXState { let resource: URI | undefined; if (input instanceof DiffEditorInput) { - resource = input.modifiedInput.getResource(); + resource = input.modifiedInput.resource; } else { - resource = input.getResource(); + resource = input.resource; } if (resource?.scheme === BulkEditPreviewProvider.Schema) { @@ -111,6 +111,7 @@ class BulkEditPreviewContribution { private async _previewEdit(edit: WorkspaceEdit) { this._ctxEnabled.set(true); + const uxState = this._activeSession?.uxState ?? new UXState(this._panelService, this._editorGroupsService); const view = await getBulkEditPane(this._viewsService); if (!view) { this._ctxEnabled.set(false); @@ -136,9 +137,9 @@ class BulkEditPreviewContribution { let session: PreviewSession; if (this._activeSession) { this._activeSession.cts.dispose(true); - session = new PreviewSession(this._activeSession.uxState); + session = new PreviewSession(uxState); } else { - session = new PreviewSession(new UXState(this._panelService, this._editorGroupsService)); + session = new PreviewSession(uxState); } this._activeSession = session; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 0970caecdbb..8147aeb6c52 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, TreeResourceNavigator } 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 { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, 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'; @@ -38,6 +38,7 @@ import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataT import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const enum State { Data = 'data', @@ -86,10 +87,11 @@ export class BulkEditPane extends ViewPane { @IConfigurationService configurationService: IConfigurationService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { super( { ...options, titleMenuId: MenuId.BulkEditTitle }, - keybindingService, contextMenuService, configurationService, _contextKeyService, viewDescriptorService, _instaService, openerService, themeService + keybindingService, contextMenuService, configurationService, _contextKeyService, viewDescriptorService, _instaService, openerService, themeService, telemetryService ); this.element.classList.add('bulk-edit-panel', 'show-file-icons'); @@ -130,7 +132,6 @@ export class BulkEditPane extends ViewPane { this._treeDataSource, { accessibilityProvider: this._instaService.createInstance(BulkEditAccessibilityProvider), - ariaProvider: new BulkEditAriaProvider(), identityProvider: new BulkEditIdentityProvider(), expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, @@ -377,7 +378,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 544fa1027d4..c6f547c7e50 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -18,13 +18,15 @@ import { BulkFileOperations, BulkFileOperation, BulkFileOperationType, BulkTextE import { FileKind } from 'vs/platform/files/common/files'; import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; -import type { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import type { IAriaProvider } from 'vs/base/browser/ui/list/listView'; +import type { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { basename } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; 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 @@ -173,7 +175,10 @@ export class BulkEditDataSource implements IAsyncDataSource new FileElement(element, op)); + return [...Iterable.map(element.category.fileOperations, op => new FileElement(element, op))]; } // file: text edit @@ -209,7 +214,7 @@ 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()); } @@ -276,18 +268,18 @@ export class BulkEditSorter implements ITreeSorter { return 0; } - - private static _needsConfirmation(a: BulkCategory): boolean { - return a.fileOperations.some(ops => ops.needsConfirmation()); - } } // --- ACCESSI -export class BulkEditAccessibilityProvider implements IAccessibilityProvider { +export class BulkEditAccessibilityProvider implements IListAccessibilityProvider { constructor(@ILabelService private readonly _labelService: ILabelService) { } + getRole(_element: BulkEditElement): string { + return 'checkbox'; + } + getAriaLabel(element: BulkEditElement): string | null { if (element instanceof FileElement) { if (element.edit.textEdits.length > 0) { @@ -369,21 +361,6 @@ export class BulkEditIdentityProvider implements IIdentityProvider { - - getSetSize(_element: BulkEditElement, _index: number, listLength: number): number { - return listLength; - } - - getPosInSet(_element: BulkEditElement, index: number): number { - return index; - } - - getRole?(_element: BulkEditElement): string { - return 'checkbox'; - } -} - // --- RENDERER class CategoryElementTemplate { @@ -420,6 +397,12 @@ export class CategoryElementRenderer implements ITreeRenderer() { - onFileChanges = Event.None; + onDidFilesChange = Event.None; async exists() { return true; } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 55b2be2978c..6ffd11e782c 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CallHierarchyTreePeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek'; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 32450e7f538..7f9a739501d 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/callHierarchy'; import * as peekView from 'vs/editor/contrib/peekView/peekView'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; import { FuzzyScore } from 'vs/base/common/filters'; import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree'; @@ -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, @@ -199,6 +199,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { container.appendChild(treeContainer); const options: IWorkbenchAsyncDataTreeOptions = { sorter: new callHTree.Sorter(), + accessibilityProvider: new callHTree.AccessibilityProvider(() => this._direction), identityProvider: new callHTree.IdentityProvider(() => this._direction), ariaLabel: localize('tree.aria', "Call Hierarchy"), expandOnlyOnTwistieClick: true, diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index 2e9cec145d3..445d67916f0 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; -import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyModel, } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyModel, } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; @@ -13,6 +13,8 @@ import { SymbolKinds, Location } from 'vs/editor/common/modes'; import * as dom from 'vs/base/browser/dom'; import { compare } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { localize } from 'vs/nls'; export class Call { constructor( @@ -138,3 +140,18 @@ export class VirtualDelegate implements IListVirtualDelegate { return CallRenderer.id; } } + +export class AccessibilityProvider implements IListAccessibilityProvider { + + constructor( + public getDirection: () => CallHierarchyDirection + ) { } + + getAriaLabel(element: Call): string | null { + if (this.getDirection() === CallHierarchyDirection.CallsFrom) { + return localize('from', "calls from {0}", element.item.name); + } else { + return localize('to', "callers fo {0}", element.item.name); + } + } +} 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/callHierarchy/browser/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts similarity index 100% rename from src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts rename to src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts 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/codeActions/common/documentationContribution.ts b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts index 9c2b75c277d..1b6507eed9d 100644 --- a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts @@ -10,7 +10,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { DocumentationExtensionPoint } from './documentationExtensionPoint'; @@ -20,7 +20,7 @@ export class CodeActionDocumentationContribution extends Disposable implements I private contributions: { title: string; - when: ContextKeyExpr; + when: ContextKeyExpression; command: string; }[] = []; 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 dc8c179721c..b9992fde979 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -9,6 +9,11 @@ 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'; import './toggleRenderControlCharacter'; 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..a8810e4b0cf 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -17,7 +17,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { FontStyle, LanguageIdentifier, StandardTokenType, TokenMetadata, DocumentSemanticTokensProviderRegistry, SemanticTokensLegend, SemanticTokens, LanguageId, ColorId } from 'vs/editor/common/modes'; +import { FontStyle, LanguageIdentifier, StandardTokenType, TokenMetadata, DocumentSemanticTokensProviderRegistry, SemanticTokensLegend, SemanticTokens, LanguageId, ColorId, DocumentRangeSemanticTokensProviderRegistry } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -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(position); + 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; } @@ -301,9 +307,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const properties: (keyof TokenStyleData)[] = ['foreground', 'bold', 'italic', 'underline']; const propertiesByDefValue: { [rule: string]: string[] } = {}; const allDefValues = []; // remember the order - // first collect to detect when the same rule is used fro multiple properties + // first collect to detect when the same rule is used for 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]; @@ -470,7 +476,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return token && token.data; } - private async _computeSemanticTokens(): Promise { + private async _computeSemanticTokens(position: Position): Promise { if (!this._isSemanticColoringEnabled()) { return null; } @@ -483,11 +489,22 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return { tokens, legend: provider.getLegend() }; } } + const rangeTokenProviders = DocumentRangeSemanticTokensProviderRegistry.ordered(this._model); + if (rangeTokenProviders.length) { + const provider = rangeTokenProviders[0]; + const lineNumber = position.lineNumber; + const range = new Range(lineNumber, 1, lineNumber, this._model.getLineMaxColumn(lineNumber)); + const tokens = await Promise.resolve(provider.provideDocumentRangeSemanticTokens(this._model, range, this._currentRequestCancellationTokenSource.token)); + if (this.isSemanticTokens(tokens)) { + return { tokens, legend: provider.getLegend() }; + } + } return null; } 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 +518,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 +545,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 new file mode 100644 index 00000000000..48756921c1d --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -0,0 +1,370 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import * as strings from 'vs/base/common/strings'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { ITextModel } from 'vs/editor/common/model'; +import { CodeActionTriggerType, DocumentFormattingEditProvider, CodeActionProvider } from 'vs/editor/common/modes'; +import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; +import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; +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 { 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'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; + +export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + // Nothing + } + + 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); + } + } + + private doTrimTrailingWhitespace(model: ITextModel, isAutoSaved: boolean): void { + let prevSelection: Selection[] = []; + let cursors: Position[] = []; + + const editor = findEditor(model, this.codeEditorService); + if (editor) { + // Find `prevSelection` in any case do ensure a good undo stack when pushing the edit + // Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump + prevSelection = editor.getSelections(); + if (isAutoSaved) { + cursors = prevSelection.map(s => s.getPosition()); + const snippetsRange = SnippetController2.get(editor).getSessionEnclosingRange(); + if (snippetsRange) { + for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) { + cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber))); + } + } + } + } + + const ops = trimTrailingWhitespace(model, cursors); + if (!ops.length) { + return; // Nothing to do + } + + model.pushEditOperations(prevSelection, ops, (_edits) => prevSelection); + } +} + +function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null { + let candidate: IActiveCodeEditor | null = null; + + if (model.isAttachedToEditor()) { + for (const editor of codeEditorService.listCodeEditors()) { + if (editor.hasModel() && editor.getModel() === model) { + if (editor.hasTextFocus()) { + return editor; // favour focused editor if there are multiple + } + + candidate = editor; + } + } + } + + return candidate; +} + +export class FinalNewLineParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + // Nothing + } + + 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); + } + } + + private doInsertFinalNewLine(model: ITextModel): void { + const lineCount = model.getLineCount(); + const lastLine = model.getLineContent(lineCount); + const lastLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(lastLine) === -1; + + if (!lineCount || lastLineIsEmptyOrWhitespace) { + return; + } + + const edits = [EditOperation.insert(new Position(lineCount, model.getLineMaxColumn(lineCount)), model.getEOL())]; + const editor = findEditor(model, this.codeEditorService); + if (editor) { + editor.executeEdits('insertFinalNewLine', edits, editor.getSelections()); + } else { + model.pushEditOperations([], edits, () => null); + } + } +} + +export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + // Nothing + } + + 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); + } + } + + /** + * returns 0 if the entire file is empty or whitespace only + */ + private findLastLineWithContent(model: ITextModel): number { + for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) { + const lineContent = model.getLineContent(lineNumber); + if (strings.lastNonWhitespaceIndex(lineContent) !== -1) { + // this line has content + return lineNumber; + } + } + // no line has content + return 0; + } + + private doTrimFinalNewLines(model: ITextModel, isAutoSaved: boolean): void { + const lineCount = model.getLineCount(); + + // Do not insert new line if file does not end with new line + if (lineCount === 1) { + return; + } + + let prevSelection: Selection[] = []; + let cannotTouchLineNumber = 0; + const editor = findEditor(model, this.codeEditorService); + if (editor) { + prevSelection = editor.getSelections(); + if (isAutoSaved) { + for (let i = 0, len = prevSelection.length; i < len; i++) { + const positionLineNumber = prevSelection[i].positionLineNumber; + if (positionLineNumber > cannotTouchLineNumber) { + cannotTouchLineNumber = positionLineNumber; + } + } + } + } + + const lastLineNumberWithContent = this.findLastLineWithContent(model); + const deleteFromLineNumber = Math.max(lastLineNumberWithContent + 1, cannotTouchLineNumber + 1); + const deletionRange = model.validateRange(new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount))); + + if (deletionRange.isEmpty()) { + return; + } + + model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], _edits => prevSelection); + + if (editor) { + editor.setSelections(prevSelection); + } + } +} + +class FormatOnSaveParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + // Nothing + } + + 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; + } + + const nestedProgress = new Progress(provider => { + progress.report({ + message: localize( + 'formatting', + "Running '{0}' Formatter ([configure](command:workbench.action.openSettings?%5B%22editor.formatOnSave%22%5D)).", + provider.displayName || provider.extensionId && provider.extensionId.value || '???' + ) + }); + }); + const editorOrModel = findEditor(textEditorModel, this.codeEditorService) || textEditorModel; + await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, nestedProgress, token); + } +} + +class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + if (!model.textEditorModel) { + return; + } + + if (env.reason === SaveReason.AUTO) { + return undefined; + } + const textEditorModel = model.textEditorModel; + + 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 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; + } + return -1; + } + if (CodeActionKind.SourceFixAll.contains(b)) { + return 1; + } + return 0; + }); + } + + if (!codeActionsOnSave.length) { + return undefined; + } + + 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(textEditorModel, codeActionsOnSave, excludedActions, progress, token); + } + + private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], progress: IProgress, token: CancellationToken): Promise { + + const getActionProgress = new class implements IProgress { + private _names: string[] = []; + private _report(): void { + progress.report({ + message: localize( + 'codeaction.get', + "Getting code actions from '{0}' ([configure](command:workbench.action.openSettings?%5B%22editor.codeActionsOnSave%22%5D)).", + this._names.map(name => `'${name}'`).join(', ') + ) + }); + } + report(provider: CodeActionProvider) { + if (provider.displayName) { + this._names.push(provider.displayName); + this._report(); + } + } + }; + + for (const codeActionKind of codeActionsOnSave) { + const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, getActionProgress, token); + try { + for (const action of actionsToRun.validActions) { + progress.report({ message: localize('codeAction.apply', "Applying code action '{0}'.", action.title) }); + await this.instantiationService.invokeFunction(applyCodeAction, action); + } + } catch { + // Failure to apply a code action should not block other on save actions + } finally { + actionsToRun.dispose(); + } + } + } + + private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], progress: IProgress, token: CancellationToken) { + return getCodeActions(model, model.getFullModelRange(), { + type: CodeActionTriggerType.Auto, + filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true }, + }, progress, token); + } +} + +export class SaveParticipantsContribution extends Disposable implements IWorkbenchContribution { + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ITextFileService private readonly textFileService: ITextFileService + ) { + super(); + + this.registerSaveParticipants(); + } + + private registerSaveParticipants(): void { + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(TrimWhitespaceParticipant))); + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(CodeActionOnSaveParticipant))); + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(FormatOnSaveParticipant))); + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(FinalNewLineParticipant))); + this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(TrimFinalNewLinesParticipant))); + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(SaveParticipantsContribution, LifecyclePhase.Restored); 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/suggestEnabledInput/suggestEnabledInput.css b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css index d79f11d4ce1..aea020640bc 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css @@ -23,3 +23,8 @@ margin-top: 2px; margin-left: 1px; } + +.suggest-input-container .monaco-editor, +.suggest-input-container .monaco-editor .lines-content { + background: none; +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 003a1d771cd..045c2470162 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -212,6 +212,10 @@ export class SuggestEnabledInput extends Widget implements IThemable { })); } + public updateAriaLabel(label: string): void { + this.inputWidget.updateOptions({ ariaLabel: label }); + } + public get onFocus(): Event { return this.inputWidget.onDidFocusEditorText; } public setValue(val: string) { @@ -228,8 +232,7 @@ export class SuggestEnabledInput extends Widget implements IThemable { public style(colors: ISuggestEnabledInputStyles): void { - this.placeholderText.style.backgroundColor = - this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; + this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; this.stylingContainer.style.color = colors.inputForeground ? colors.inputForeground.toString() : ''; this.placeholderText.style.color = colors.inputPlaceholderForeground ? colors.inputPlaceholderForeground.toString() : ''; @@ -295,7 +298,6 @@ registerThemingParticipant((theme, collector) => { const backgroundColor = theme.getColor(inputBackground); if (backgroundColor) { collector.addRule(`.suggest-input-container .monaco-editor-background { background-color: ${backgroundColor}; } `); - collector.addRule(`.suggest-input-container .monaco-editor { background-color: ${backgroundColor}; } `); } }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts new file mode 100644 index 00000000000..011918eb9b8 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands'; +import { Position } from 'vs/editor/common/core/position'; +import { Selection } from 'vs/editor/common/core/selection'; +import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; + +export class ToggleColumnSelectionAction extends Action { + public static readonly ID = 'editor.action.toggleColumnSelection'; + public static readonly LABEL = nls.localize('toggleColumnSelection', "Toggle Column Selection Mode"); + + constructor( + id: string, + label: string, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService + ) { + super(id, label); + } + + private _getCodeEditor(): ICodeEditor | null { + const codeEditor = this._codeEditorService.getFocusedCodeEditor(); + if (codeEditor) { + return codeEditor; + } + return this._codeEditorService.getActiveCodeEditor(); + } + + public async run(): Promise { + const oldValue = this._configurationService.getValue('editor.columnSelection'); + const codeEditor = this._getCodeEditor(); + await this._configurationService.updateValue('editor.columnSelection', !oldValue, ConfigurationTarget.USER); + const newValue = this._configurationService.getValue('editor.columnSelection'); + if (!codeEditor || codeEditor !== this._getCodeEditor() || oldValue === newValue || !codeEditor.hasModel()) { + return; + } + const cursors = codeEditor._getCursors(); + if (codeEditor.getOption(EditorOption.columnSelection)) { + const selection = codeEditor.getSelection(); + const modelSelectionStart = new Position(selection.selectionStartLineNumber, selection.selectionStartColumn); + const viewSelectionStart = cursors.context.convertModelPositionToViewPosition(modelSelectionStart); + const modelPosition = new Position(selection.positionLineNumber, selection.positionColumn); + const viewPosition = cursors.context.convertModelPositionToViewPosition(modelPosition); + + CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursors, { + position: modelSelectionStart, + viewPosition: viewSelectionStart + }); + const visibleColumn = CursorColumns.visibleColumnFromColumn2(cursors.context.config, cursors.context.viewModel, viewPosition); + CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(cursors, { + position: modelPosition, + viewPosition: viewPosition, + doColumnSelect: true, + mouseColumn: visibleColumn + 1 + }); + } else { + const columnSelectData = cursors.getColumnSelectData(); + const fromViewColumn = CursorColumns.columnFromVisibleColumn2(cursors.context.config, cursors.context.viewModel, columnSelectData.fromViewLineNumber, columnSelectData.fromViewVisualColumn); + const fromPosition = cursors.context.convertViewPositionToModelPosition(columnSelectData.fromViewLineNumber, fromViewColumn); + const toViewColumn = CursorColumns.columnFromVisibleColumn2(cursors.context.config, cursors.context.viewModel, columnSelectData.toViewLineNumber, columnSelectData.toViewVisualColumn); + const toPosition = cursors.context.convertViewPositionToModelPosition(columnSelectData.toViewLineNumber, toViewColumn); + + codeEditor.setSelection(new Selection(fromPosition.lineNumber, fromPosition.column, toPosition.lineNumber, toPosition.column)); + } + } +} + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleColumnSelectionAction, ToggleColumnSelectionAction.ID, ToggleColumnSelectionAction.LABEL), 'Toggle Column Selection Mode'); + +MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { + group: '4_config', + command: { + id: ToggleColumnSelectionAction.ID, + title: nls.localize({ key: 'miColumnSelection', comment: ['&& denotes a mnemonic'] }, "Column &&Selection Mode"), + toggled: ContextKeyExpr.equals('config.editor.columnSelection', true) + }, + order: 2 +}); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts index 05df06aa388..bc210c5a355 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts @@ -70,7 +70,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMultiCursorModifierAction, ToggleMultiCursorModifierAction.ID, ToggleMultiCursorModifierAction.LABEL), 'Toggle Multi-Cursor Modifier'); MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { - group: '3_multi', + group: '4_config', command: { id: ToggleMultiCursorModifierAction.ID, title: nls.localize('miMultiCursorAlt', "Switch to Alt+Click for Multi-Cursor") @@ -79,7 +79,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { order: 1 }); MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { - group: '3_multi', + group: '4_config', command: { id: ToggleMultiCursorModifierAction.ID, title: ( 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/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts index 536d0f75c5c..592605c0759 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts @@ -20,10 +20,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; export class SelectionClipboard extends Disposable implements IEditorContribution { private static readonly SELECTION_LENGTH_LIMIT = 65536; @@ -119,15 +116,7 @@ class PasteSelectionClipboardAction extends EditorAction { id: 'editor.action.selectionClipboardPaste', label: nls.localize('actions.pasteSelectionClipboard', "Paste Selection Clipboard"), alias: 'Paste Selection Clipboard', - precondition: EditorContextKeys.writable, - kbOpts: { - kbExpr: ContextKeyExpr.and( - EditorContextKeys.editorTextFocus, - ContextKeyExpr.has('config.editor.selectionClipboard') - ), - primary: KeyMod.Shift | KeyCode.Insert, - weight: KeybindingWeight.EditorContrib - } + precondition: EditorContextKeys.writable }); } diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts similarity index 84% rename from src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts rename to src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts index e9c8770111e..bed0805028b 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts @@ -5,31 +5,25 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { FinalNewLineParticipant, TrimFinalNewLinesParticipant } from 'vs/workbench/api/browser/mainThreadSaveParticipant'; +import { FinalNewLineParticipant, TrimFinalNewLinesParticipant, TrimWhitespaceParticipant } from 'vs/workbench/contrib/codeEditor/browser/saveParticipants'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; -import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -class ServiceAccessor { - constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService) { - } -} - -suite('MainThreadSaveParticipant', function () { +suite('Save Participants', function () { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { @@ -157,4 +151,24 @@ suite('MainThreadSaveParticipant', function () { model.textEditorModel.redo(); assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); }); + + test('trim whitespace', async function () { + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; + + await model.load(); + const configService = new TestConfigurationService(); + configService.setUserConfiguration('files', { 'trimTrailingWhitespace': true }); + const participant = new TrimWhitespaceParticipant(configService, undefined!); + const textContent = 'Test'; + let content = `${textContent} `; + model.textEditorModel.setValue(content); + + // save many times + for (let i = 0; i < 10; i++) { + await participant.participate(model, { reason: SaveReason.EXPLICIT }); + } + + // confirm trimming + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}`); + }); }); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index fbdfdab2e22..55707657cde 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; } ); } @@ -438,6 +438,9 @@ export class CommentNode extends Disposable { this._actionsToolbarContainer.classList.remove('hidden'); this._actionsToolbarContainer.classList.add('tabfocused'); this._domNode.tabIndex = 0; + if (this.comment.mode === modes.CommentMode.Editing) { + this._commentEditor?.focus(); + } } else { if (this._actionsToolbarContainer.classList.contains('tabfocused') && !this._actionsToolbarContainer.classList.contains('mouseover')) { this._actionsToolbarContainer.classList.add('hidden'); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index e9f233496aa..8df8b1f71df 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; @@ -328,6 +328,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget let lastCommentElement: HTMLElement | null = null; let newCommentNodeList: CommentNode[] = []; + let newCommentsInEditMode: CommentNode[] = []; for (let i = newCommentsLen - 1; i >= 0; i--) { let currentComment = commentThread.comments![i]; let oldCommentNode = this._commentElements.filter(commentNode => commentNode.comment.uniqueIdInThread === currentComment.uniqueIdInThread); @@ -345,6 +346,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentsElement.appendChild(newElement.domNode); lastCommentElement = newElement.domNode; } + + if (currentComment.mode === modes.CommentMode.Editing) { + newElement.switchToEditMode(); + newCommentsInEditMode.push(newElement); + } } } @@ -386,6 +392,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThreadContextValue.reset(); } + if (newCommentsInEditMode.length) { + const lastIndex = this._commentElements.indexOf(newCommentsInEditMode[newCommentsInEditMode.length - 1]); + this._focusedComment = lastIndex; + } + this.setFocusedComment(this._focusedComment); } @@ -432,6 +443,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentElements.push(newCommentNode); this._commentsElement.appendChild(newCommentNode.domNode); + if (comment.mode === modes.CommentMode.Editing) { + newCommentNode.switchToEditMode(); + } } } @@ -496,7 +510,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget // If there are no existing comments, place focus on the text area. This must be done after show, which also moves focus. // if this._commentThread.comments is undefined, it doesn't finish initialization yet, so we don't focus the editor immediately. - if (this._commentThread.comments && !this._commentThread.comments.length) { + if (!this._commentThread.comments || !this._commentThread.comments.length) { this._commentEditor.focus(); } else if (this._commentEditor.getModel()!.getValueLength() > 0) { this.expandReplyArea(); @@ -728,12 +742,20 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const computedLinesNumber = Math.ceil((headHeight + dimensions.height + arrowHeight + frameThickness + 8 /** margin bottom to avoid margin collapse */) / lineHeight); + if (this._viewZone?.heightInLines === computedLinesNumber) { + return; + } + let currentPosition = this.getPosition(); if (this._viewZone && currentPosition && currentPosition.lineNumber !== this._viewZone.afterLineNumber) { this._viewZone.afterLineNumber = currentPosition.lineNumber; } + if (!this._commentThread.comments || !this._commentThread.comments.length) { + this._commentEditor.focus(); + } + this._relayout(computedLinesNumber); } } @@ -758,7 +780,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 +850,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..6f53ec80720 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -20,7 +20,8 @@ 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'; +import { basename } from 'vs/base/common/resources'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_TITLE = 'Comments'; @@ -149,10 +150,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, @@ -176,6 +182,27 @@ export class CommentsList extends WorkbenchAsyncDataTree { renderers, dataSource, { + accessibilityProvider: { + getAriaLabel(element: any): string { + if (element instanceof CommentsModel) { + return nls.localize('rootCommentsLabel', "Comments for current workspace"); + } + if (element instanceof ResourceWithCommentThreads) { + return nls.localize('resourceWithCommentThreadsLabel', "Comments in {0}, full path {1}", basename(element.resource), element.resource.fsPath); + } + if (element instanceof CommentNode) { + return nls.localize('resourceWithCommentLabel', + "Comment from ${0} at line {1} column {2} in {3}, source: {4}", + element.comment.userName, + element.range.startLineNumber, + element.range.startColumn, + basename(element.resource), + element.comment.body.value + ); + } + return ''; + } + }, ariaLabel: COMMENTS_VIEW_TITLE, keyboardSupport: true, identityProvider: { @@ -202,9 +229,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 ebd2b1ae5de..53c2f1fbd9e 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'; @@ -27,6 +26,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; 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 { TreeResourceNavigator } from 'vs/platform/list/browser/listService'; export class CommentsPanel extends ViewPane { @@ -51,9 +52,10 @@ export class CommentsPanel extends ViewPane { @IKeybindingService keybindingService: IKeybindingService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, - @ICommentService private readonly commentService: ICommentService + @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); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } public renderBody(container: HTMLElement): void { @@ -73,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) { @@ -87,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}; }`); @@ -119,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); } @@ -147,7 +149,7 @@ 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 })); this._register(commentsNavigator.onDidOpenResource(e => { @@ -167,11 +169,11 @@ export class CommentsPanel extends ViewPane { const range = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].range : element.range; const activeEditor = this.editorService.activeEditor; - let currentActiveResource = activeEditor ? activeEditor.getResource() : undefined; + let currentActiveResource = activeEditor ? activeEditor.resource : undefined; 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/configExporter/node/configurationExportHelper.contribution.ts b/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.contribution.ts similarity index 83% rename from src/vs/workbench/contrib/configExporter/node/configurationExportHelper.contribution.ts rename to src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.contribution.ts index 503f8a83d5a..5a989bee849 100644 --- a/src/vs/workbench/contrib/configExporter/node/configurationExportHelper.contribution.ts +++ b/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.contribution.ts @@ -8,13 +8,14 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { DefaultConfigurationExportHelper } from 'vs/workbench/contrib/configExporter/node/configurationExportHelper'; +import { DefaultConfigurationExportHelper } from 'vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class ExtensionPoints implements IWorkbenchContribution { constructor( @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService ) { // Config Exporter if (environmentService.configuration['export-default-configuration']) { diff --git a/src/vs/workbench/contrib/configExporter/node/configurationExportHelper.ts b/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.ts similarity index 85% rename from src/vs/workbench/contrib/configExporter/node/configurationExportHelper.ts rename to src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.ts index 1882b46ebfa..083e5881f22 100644 --- a/src/vs/workbench/contrib/configExporter/node/configurationExportHelper.ts +++ b/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.ts @@ -5,7 +5,8 @@ import { writeFile } from 'vs/base/node/pfs'; import product from 'vs/platform/product/common/product'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -30,7 +31,7 @@ interface IConfigurationExport { export class DefaultConfigurationExportHelper { constructor( - @IEnvironmentService environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IExtensionService private readonly extensionService: IExtensionService, @ICommandService private readonly commandService: ICommandService) { if (environmentService.args['export-default-configuration']) { @@ -41,7 +42,7 @@ export class DefaultConfigurationExportHelper { private writeConfigModelAndQuit(targetPath: string): Promise { return Promise.resolve(this.extensionService.whenInstalledExtensionsRegistered()) .then(() => this.writeConfigModel(targetPath)) - .then(() => this.commandService.executeCommand('workbench.action.quit')) + .finally(() => this.commandService.executeCommand('workbench.action.quit')) .then(() => { }); } @@ -56,8 +57,14 @@ export class DefaultConfigurationExportHelper { const configRegistry = Registry.as(Extensions.Configuration); const configurations = configRegistry.getConfigurations().slice(); const settings: IExportedConfigurationNode[] = []; + const processedNames = new Set(); const processProperty = (name: string, prop: IConfigurationPropertySchema) => { + if (processedNames.has(name)) { + throw new Error('Setting is registered twice: ' + name); + } + + processedNames.add(name); const propDetails: IExportedConfigurationNode = { name, description: prop.description || prop.markdownDescription || '', diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 6814383e70b..5c9adc3ce2c 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -11,16 +11,16 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; +import type { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorCommandsContext } from 'vs/workbench/common/editor'; -import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; -import { defaultEditorId } from 'vs/workbench/contrib/customEditor/browser/customEditors'; -import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { defaultCustomEditor } from 'vs/workbench/contrib/customEditor/common/contributedCustomEditors'; +import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import type { ITextEditorOptions } from 'vs/platform/editor/common/editor'; const viewCategory = nls.localize('viewCategory', "View"); @@ -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, @@ -56,7 +56,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ group = editorGroupService.getGroup(editorContext.groupId); } else if (!resource) { if (editorService.activeEditor) { - resource = editorService.activeEditor.getResource(); + resource = editorService.activeEditor.resource; group = editorGroupService.activeGroup; } } @@ -80,12 +80,22 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { title: REOPEN_WITH_TITLE, category: viewCategory, }, - when: CONTEXT_HAS_CUSTOM_EDITORS, + when: CONTEXT_CUSTOM_EDITORS.notEqualsTo(''), +}); + +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: REOPEN_WITH_COMMAND_ID, + title: REOPEN_WITH_TITLE, + category: viewCategory, + }, + group: '6_reopen', + order: 20, + when: CONTEXT_CUSTOM_EDITORS.notEqualsTo(''), }); // #endregion - (new class UndoCustomEditorCommand extends Command { public static readonly ID = 'editor.action.customEditor.undo'; @@ -103,19 +113,11 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { } public runCommand(accessor: ServicesAccessor): void { - const customEditorService = accessor.get(ICustomEditorService); - - const activeCustomEditor = customEditorService.activeCustomEditor; - if (!activeCustomEditor) { - return; + const editorService = accessor.get(IEditorService); + const activeInput = editorService.activeEditorPane?.input; + if (activeInput instanceof CustomEditorInput) { + activeInput.undo(); } - - const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType); - if (!model) { - return; - } - - model.undo(); } }).register(); @@ -138,19 +140,11 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { } public runCommand(accessor: ServicesAccessor): void { - const customEditorService = accessor.get(ICustomEditorService); - - const activeCustomEditor = customEditorService.activeCustomEditor; - if (!activeCustomEditor) { - return; + const editorService = accessor.get(IEditorService); + const activeInput = editorService.activeEditorPane?.input; + if (activeInput instanceof CustomEditorInput) { + activeInput.redo(); } - - const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType); - if (!model) { - return; - } - - model.redo(); } }).register(); @@ -160,20 +154,20 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { constructor() { super({ id: ToggleCustomEditorCommand.ID, - precondition: CONTEXT_HAS_CUSTOM_EDITORS, + precondition: CONTEXT_CUSTOM_EDITORS, }); } 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 targetResource = activeEditor.getResource(); + const activeGroup = activeEditorPane.group; + const activeEditor = activeEditorPane.input; + const targetResource = activeEditor.resource; if (!targetResource) { return; @@ -181,8 +175,8 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { const customEditorService = accessor.get(ICustomEditorService); - let toggleView = defaultEditorId; - if (!(activeEditor instanceof CustomFileEditorInput)) { + let toggleView = defaultCustomEditor.id; + if (!(activeEditor instanceof CustomEditorInput)) { const bestAvailableEditor = customEditorService.getContributedCustomEditors(targetResource).bestAvailableEditor; if (bestAvailableEditor) { toggleView = bestAvailableEditor.id; @@ -191,7 +185,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { } } - 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/webviewEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts similarity index 55% rename from src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts rename to src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts index 663521b2042..efb425cc224 100644 --- a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts @@ -3,65 +3,44 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; 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 { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { CustomEditorInputFactory } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import './commands'; -import { CustomFileEditorInput } from './customEditorInput'; -import { CustomEditorContribution, customEditorsAssociationsKey, CustomEditorService } from './customEditors'; +import { CustomEditorInput } from './customEditorInput'; +import { CustomEditorContribution, CustomEditorService } from './customEditors'; +import { editorAssociationsConfigurationNode } from './editorAssociationsSetting'; registerSingleton(ICustomEditorService, CustomEditorService); Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(CustomEditorContribution, LifecyclePhase.Starting); -Registry.as(EditorExtensions.Editors).registerEditor( - EditorDescriptor.create( - WebviewEditor, - WebviewEditor.ID, - 'Webview Editor', - ), [ - new SyncDescriptor(CustomFileEditorInput) -]); +Registry.as(EditorExtensions.Editors) + .registerEditor( + EditorDescriptor.create( + WebviewEditor, + WebviewEditor.ID, + 'Webview Editor', + ), [ + new SyncDescriptor(CustomEditorInput) + ]); -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( - CustomEditorInputFactory.ID, - CustomEditorInputFactory); +Registry.as(EditorInputExtensions.EditorInputFactories) + .registerEditorInputFactory( + 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."), - items: { - type: 'object', - 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."), - }, - 'filenamePattern': { - type: 'string', - description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern the editor should be used for."), - } - } - } - } - } - }); + .registerConfiguration(editorAssociationsConfigurationNode); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 57b85f7a094..8dd372017e6 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -5,34 +5,42 @@ 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 { WebviewEditorOverlay, IWebviewService } 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 { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { +export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { public static typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; - private _model?: ICustomEditorModel; + private readonly _startsDirty: boolean | undefined; + + public readonly backupId: string | undefined; + + get resource() { return this._editorResource; } + + private _modelRef?: IReference; constructor( resource: URI, viewType: string, id: string, - webview: Lazy, + webview: Lazy, + options: { startsDirty?: boolean, backupId?: string }, @IWebviewService webviewService: IWebviewService, @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -40,18 +48,17 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @ICustomEditorService private readonly customEditorService: ICustomEditorService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IUndoRedoService private readonly undoRedoService: IUndoRedoService, ) { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; + this._startsDirty = options.startsDirty; + this.backupId = options.backupId; } public getTypeId(): string { - return CustomFileEditorInput.typeId; - } - - public getResource(): URI { - return this._editorResource; + return CustomEditorInput.typeId; } public supportsSplitEditor() { @@ -60,18 +67,13 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @memoize getName(): string { - return basename(this.labelService.getUriLabel(this.getResource())); - } - - @memoize - getDescription(): string | undefined { - return super.getDescription(); + return basename(this.labelService.getUriLabel(this.resource)); } matches(other: IEditorInput): boolean { - return this === other || (other instanceof CustomFileEditorInput + return this === other || (other instanceof CustomEditorInput && this.viewType === other.viewType - && isEqual(this.getResource(), other.getResource())); + && isEqual(this.resource, other.resource)); } @memoize @@ -81,12 +83,12 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @memoize private get mediumTitle(): string { - return this.labelService.getUriLabel(this.getResource(), { relative: true }); + return this.labelService.getUriLabel(this.resource, { relative: true }); } @memoize private get longTitle(): string { - return this.labelService.getUriLabel(this.getResource()); + return this.labelService.getUriLabel(this.resource); } public getTitle(verbosity?: Verbosity): string { @@ -102,11 +104,18 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { } public isReadonly(): boolean { - return false; + return this._modelRef ? this._modelRef.object.isReadonly() : false; + } + + public isUntitled(): boolean { + return this.resource.scheme === Schemas.untitled; } public isDirty(): boolean { - return this._model ? this._model.isDirty() : false; + if (!this._modelRef) { + return !!this._startsDirty; + } + return this._modelRef.object.isDirty(); } public isSaving(): boolean { @@ -122,61 +131,109 @@ export class CustomFileEditorInput 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 } - const result = await this._model.save(options); - if (!result) { - return undefined; + 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 } - if (!await this._model.saveAs(this._editorResource, target, options)) { + 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 revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - return this._model ? this._model.revert(options) : Promise.resolve(false); + public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + return assertIsDefined(this._modelRef).object.revert(options); } - public async resolve(): Promise { - this._model = await this.customEditorService.models.resolve(this.getResource(), this.viewType); - this._register(this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - if (this.isDirty()) { - this._onDidChangeDirty.fire(); + public async resolve(): Promise { + await super.resolve(); + + if (this.isDisposed()) { + return null; } - return await super.resolve(); + + 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())); + + if (this.isDirty()) { + this._onDidChangeDirty.fire(); + } + } + + 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(CustomFileEditorInput, - 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, backupId: this.backupId }); // this webview is replaced in the transfer call + this.transfer(newEditor); + newEditor.updateGroup(group); + return newEditor; + } + + public undo(): void { + assertIsDefined(this._modelRef); + this.undoRedoService.undo(this.resource); + } + + public redo(): void { + 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; + } + + 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 866f96ef5f5..34962e56edd 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -3,29 +3,58 @@ * 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 { CustomFileEditorInput } 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; + backupId: string; + + 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 = CustomFileEditorInput.typeId; + 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: CustomFileEditorInput): string | undefined { - const data = { + public serialize(input: CustomEditorInput): string | undefined { + const data: SerializedCustomEditor = { ...this.toJson(input), - editorResource: input.getResource().toJSON() + editorResource: input.resource.toJSON(), + dirty: input.isDirty(), }; try { @@ -38,22 +67,63 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { public deserialize( _instantiationService: IInstantiationService, serializedEditorInput: string - ): CustomFileEditorInput { + ): CustomEditorInput { const data = this.fromJson(serializedEditorInput); 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(CustomFileEditorInput, 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); } 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, backupId: backupData.backupId }); + 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 07351da72e8..48d67977a40 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -4,107 +4,47 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce } from 'vs/base/common/arrays'; -import { Emitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; -import { basename, isEqual } from 'vs/base/common/resources'; +import { basename, extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; 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_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, 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 { CustomFileEditorInput } from './customEditorInput'; - -export const defaultEditorId = 'default'; - -const defaultEditorInfo = new CustomEditorInfo({ - id: defaultEditorId, - displayName: nls.localize('promptOpenWith.defaultEditor', "VS Code's standard text editor"), - selector: [ - { filenamePattern: '*' } - ], - priority: CustomEditorPriority.default, -}); - -export class CustomEditorInfoStore extends Disposable { - - private readonly _contributedEditors = new Map(); - - constructor() { - super(); - - webviewEditorsExtensionPoint.setHandler(extensions => { - this._contributedEditors.clear(); - - for (const extension of extensions) { - for (const webviewEditorContribution of extension.value) { - this.add(new CustomEditorInfo({ - id: webviewEditorContribution.viewType, - displayName: webviewEditorContribution.displayName, - selector: webviewEditorContribution.selector || [], - priority: webviewEditorContribution.priority || CustomEditorPriority.default, - })); - } - } - this._onChange.fire(); - }); - } - - private readonly _onChange = this._register(new Emitter()); - public readonly onChange = this._onChange.event; - - public get(viewType: string): CustomEditorInfo | undefined { - return viewType === defaultEditorId - ? defaultEditorInfo - : this._contributedEditors.get(viewType); - } - - public getContributedEditors(resource: URI): readonly CustomEditorInfo[] { - return Array.from(this._contributedEditors.values()) - .filter(customEditor => customEditor.matches(resource)); - } - - private add(info: CustomEditorInfo): void { - if (info.id === defaultEditorId || this._contributedEditors.has(info.id)) { - console.error(`Custom editor with id '${info.id}' already registered`); - return; - } - this._contributedEditors.set(info.id, info); - } -} +import { ContributedCustomEditors, defaultCustomEditor } from '../common/contributedCustomEditors'; +import { CustomEditorInput } from './customEditorInput'; +import { CustomEditorAssociation, CustomEditorAssociationsSettingIntelliSense, CustomEditorsAssociations, customEditorsAssociationsSettingId } from './editorAssociationsSetting'; export class CustomEditorService extends Disposable implements ICustomEditorService { _serviceBrand: any; - private readonly _editorInfoStore = this._register(new CustomEditorInfoStore()); + private readonly _contributedEditors = this._register(new ContributedCustomEditors()); - private readonly _models: CustomEditorModelManager; + private readonly _models = new CustomEditorModelManager(); - private readonly _hasCustomEditor: IContextKey; + private readonly _customEditorContextKey: IContextKey; private readonly _focusedCustomEditorIsEditable: IContextKey; private readonly _webviewHasOwnEditFunctions: IContextKey; constructor( @IContextKeyService contextKeyService: IContextKeyService, - @IWorkingCopyService workingCopyService: IWorkingCopyService, @IFileService fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @@ -112,20 +52,20 @@ 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._hasCustomEditor = CONTEXT_HAS_CUSTOM_EDITORS.bindTo(contextKeyService); + this._customEditorContextKey = CONTEXT_CUSTOM_EDITORS.bindTo(contextKeyService); this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); - this._register(this._editorInfoStore.onChange(() => this.updateContexts())); + this._register(new CustomEditorAssociationsSettingIntelliSense(this._contributedEditors)); + this._register(this._contributedEditors.onChange(() => { + this.updateContexts(); + })); this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts())); - this._register(fileService.onAfterOperation(e => { + this._register(fileService.onDidRunOperation(e => { if (e.isOperation(FileOperation.MOVE)) { this.handleMovedFileInOpenedFileEditors(e.resource, e.target.resource); } @@ -136,65 +76,110 @@ 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 CustomFileEditorInput)) { - return undefined; - } - const resource = activeInput.getResource(); - return { resource, viewType: activeInput.viewType }; - } - public getCustomEditor(viewType: string): CustomEditorInfo | undefined { - return this._editorInfoStore.get(viewType); + return this._contributedEditors.get(viewType); } public getContributedCustomEditors(resource: URI): CustomEditorInfoCollection { - return new CustomEditorInfoCollection(this._editorInfoStore.getContributedEditors(resource)); + return new CustomEditorInfoCollection(this._contributedEditors.getContributedEditors(resource)); } public getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection { - const rawAssociations = this.configurationService.getValue(customEditorsAssociationsKey) || []; + const rawAssociations = this.configurationService.getValue(customEditorsAssociationsSettingId) || []; return new CustomEditorInfoCollection( coalesce(rawAssociations .filter(association => CustomEditorInfo.selectorMatches(association, resource)) - .map(association => this._editorInfoStore.get(association.viewType)))); + .map(association => this._contributedEditors.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, + defaultCustomEditor, + ...this.getAllCustomEditors(resource).allEditors, ]); let currentlyOpenedEditorType: undefined | string; for (const editor of group ? group.editors : []) { - if (editor.getResource() && isEqual(editor.getResource(), resource)) { - currentlyOpenedEditorType = editor instanceof CustomFileEditorInput ? editor.viewType : defaultEditorId; + if (editor.resource && isEqual(editor.resource, resource)) { + currentlyOpenedEditorType = editor instanceof CustomEditorInput ? editor.viewType : defaultCustomEditor.id; break; } } + const resourceExt = extname(resource); + const items = customEditors.allEditors.map((editorDescriptor): IQuickPickItem => ({ label: editorDescriptor.displayName, id: editorDescriptor.id, description: editorDescriptor.id === currentlyOpenedEditorType ? nls.localize('openWithCurrentlyActive', "Currently Active") - : undefined + : undefined, + detail: editorDescriptor.providerDisplayName, + buttons: resourceExt ? [{ + iconClass: 'codicon-settings-gear', + tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) + }] : undefined })); - const pick = await this.quickInputService.pick(items, { - placeHolder: nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource)), - }); - if (!pick || !pick.id) { - return; - } - return this.openWith(resource, pick.id, options, group); + const picker = this.quickInputService.createQuickPick(); + picker.items = items; + picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource)); + + return new Promise(resolve => { + picker.onDidAccept(() => { + resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0].id : undefined); + picker.dispose(); + }); + picker.onDidTriggerItemButton(e => { + const pick = e.item.id; + resolve(pick); // open the view + picker.dispose(); + + // And persist the setting + if (pick) { + const newAssociation: CustomEditorAssociation = { viewType: pick, filenamePattern: '*' + resourceExt }; + const currentAssociations = [...this.configurationService.getValue(customEditorsAssociationsSettingId)] || []; + + // First try updating existing association + for (let i = 0; i < currentAssociations.length; ++i) { + const existing = currentAssociations[i]; + if (existing.filenamePattern === newAssociation.filenamePattern) { + currentAssociations.splice(i, 1, newAssociation); + this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); + return; + } + } + + // Otherwise, create a new one + currentAssociations.unshift(newAssociation); + this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); + } + }); + picker.show(); + }); } public openWith( @@ -202,37 +187,37 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ viewType: string, options?: ITextEditorOptions, group?: IEditorGroup, - ): Promise { - if (viewType === defaultEditorId) { - const fileInput = this.editorService.createInput({ resource, forceFile: true }); - return this.openEditorForResource(resource, fileInput, { ...options, ignoreOverrides: true }, group); + ): Promise { + if (viewType === defaultCustomEditor.id) { + const fileEditorInput = this.editorService.createEditorInput({ resource, forceFile: true }); + return this.openEditorForResource(resource, fileEditorInput, { ...options, ignoreOverrides: true }, group); } - if (!this._editorInfoStore.get(viewType)) { + if (!this._contributedEditors.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 }); + if (viewType === defaultCustomEditor.id) { + 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(CustomFileEditorInput, 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; } @@ -242,11 +227,15 @@ 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.getResource() && isEqual(editor.getResource(), resource)); + const existingEditors = targetGroup.editors.filter(editor => editor.resource && isEqual(editor.resource, resource)); if (existingEditors.length) { const existing = existingEditors[0]; if (!input.matches(existing)) { @@ -256,7 +245,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ options: options ? EditorOptions.create(options) : undefined, }], targetGroup); - if (existing instanceof CustomFileEditorInput) { + if (existing instanceof CustomEditorInput) { existing.dispose(); } } @@ -266,54 +255,87 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } private updateContexts() { - const activeControl = this.editorService.activeControl; - const resource = activeControl?.input.getResource(); + const activeEditorPane = this.editorService.activeEditorPane; + const resource = activeEditorPane?.input?.resource; if (!resource) { - this._hasCustomEditor.reset(); + this._customEditorContextKey.reset(); this._focusedCustomEditorIsEditable.reset(); this._webviewHasOwnEditFunctions.reset(); return; } - const possibleEditors = [ - ...this.getContributedCustomEditors(resource).allEditors, - ...this.getUserConfiguredCustomEditors(resource).allEditors, - ]; - this._hasCustomEditor.set(possibleEditors.length > 0); - this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput); + const possibleEditors = this.getAllCustomEditors(resource).allEditors; + + this._customEditorContextKey.set(possibleEditors.map(x => x.id).join(',')); + 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 CustomFileEditorInput)) { - 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) { - continue; - } - - 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 type CustomEditorsAssociations = readonly (CustomEditorSelector & { readonly viewType: string; })[]; - export class CustomEditorContribution extends Disposable implements IWorkbenchContribution { constructor( @IEditorService private readonly editorService: EditorServiceImpl, @@ -321,12 +343,14 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo ) { super(); - this._register(this.editorService.overrideOpenEditor((editor, options, group) => { - return this.onEditorOpening(editor, options, group); + this._register(this.editorService.overrideOpenEditor({ + open: (editor, options, group) => { + return this.onEditorOpening(editor, options, group); + } })); this._register(this.editorService.onDidCloseEditor(({ editor }) => { - if (!(editor instanceof CustomFileEditorInput)) { + if (!(editor instanceof CustomEditorInput)) { return; } @@ -341,7 +365,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo options: ITextEditorOptions | undefined, group: IEditorGroup ): IOpenEditorOverride | undefined { - if (editor instanceof CustomFileEditorInput) { + if (editor instanceof CustomEditorInput) { if (editor.group === group.id) { // No need to do anything return undefined; @@ -350,7 +374,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo // Unlike normal editor inputs, we do not want to share custom editor inputs // between multiple editors / groups. return { - override: this.customEditorService.openWith(editor.getResource(), editor.viewType, options, group) + override: this.customEditorService.openWith(editor.resource, editor.viewType, options, group) }; } } @@ -359,7 +383,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo return this.onDiffEditorOpening(editor, options, group); } - const resource = editor.getResource(); + const resource = editor.resource; if (resource) { return this.onResourceEditorOpening(resource, editor, options, group); } @@ -382,10 +406,14 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo // If there is, we want to open that instead of creating a new editor. // This ensures that we preserve whatever type of editor was previously being used // when the user switches back to it. - const existingEditorForResource = group.editors.find(editor => isEqual(resource, editor.getResource())); + 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) }; } @@ -438,10 +466,10 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo group: IEditorGroup ): IOpenEditorOverride | undefined { const getCustomEditorOverrideForSubInput = (subInput: IEditorInput, customClasses: string): EditorInput | undefined => { - if (subInput instanceof CustomFileEditorInput) { + if (subInput instanceof CustomEditorInput) { return undefined; } - const resource = subInput.getResource(); + const resource = subInput.resource; if (!resource) { return undefined; } @@ -457,7 +485,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; } @@ -481,6 +509,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo } } + registerThemingParticipant((theme, collector) => { const shadow = theme.getColor(colorRegistry.scrollbarShadow); if (shadow) { diff --git a/src/vs/workbench/contrib/customEditor/browser/editorAssociationsSetting.ts b/src/vs/workbench/contrib/customEditor/browser/editorAssociationsSetting.ts new file mode 100644 index 00000000000..99098d7b517 --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/browser/editorAssociationsSetting.ts @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { CustomEditorSelector } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { ContributedCustomEditors, defaultCustomEditor } from 'vs/workbench/contrib/customEditor/common/contributedCustomEditors'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export const customEditorsAssociationsSettingId = 'workbench.editorAssociations'; + +export type CustomEditorAssociation = CustomEditorSelector & { + readonly viewType: string; +}; + +export type CustomEditorsAssociations = readonly CustomEditorAssociation[]; + +const viewTypeSchamaAddition: IJSONSchema = { + type: 'string', + enum: [] +}; + +export const editorAssociationsConfigurationNode: IConfigurationNode = { + ...workbenchConfigurationNodeBase, + properties: { + [customEditorsAssociationsSettingId]: { + type: 'array', + 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': { + anyOf: [ + { + type: 'string', + description: nls.localize('editor.editorAssociations.viewType', "The unique id of the editor to use."), + }, + viewTypeSchamaAddition + ] + }, + 'filenamePattern': { + type: 'string', + description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."), + } + } + } + } + } +}; + +export class CustomEditorAssociationsSettingIntelliSense extends Disposable { + + constructor( + private readonly _contributedCustomEditors: ContributedCustomEditors, + ) { + super(); + + this._register(_contributedCustomEditors.onChange(() => { + this.updateSchema(); + })); + this.updateSchema(); + } + + private updateSchema() { + const enumValues: string[] = []; + const enumDescriptions: string[] = []; + for (const info of [defaultCustomEditor, ...this._contributedCustomEditors]) { + enumValues.push(info.id); + enumDescriptions.push(nls.localize('editorAssociations.viewType.sourceDescription', "Source: {0}", info.providerDisplayName)); + } + viewTypeSchamaAddition.enum = enumValues; + viewTypeSchamaAddition.enumDescriptions = enumDescriptions; + + Registry.as(Extensions.Configuration) + .notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode); + } +} diff --git a/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.ts new file mode 100644 index 00000000000..c52cdbe1d58 --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/common/contributedCustomEditors.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 { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import * as nls from 'vs/nls'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { CustomEditorInfo, CustomEditorPriority } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { ICustomEditorsExtensionPoint, customEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/common/extensionPoint'; + +const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); + +export const defaultCustomEditor = new CustomEditorInfo({ + id: 'default', + displayName: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), + providerDisplayName: builtinProviderDisplayName, + selector: [ + { filenamePattern: '*' } + ], + priority: CustomEditorPriority.default, +}); + +export class ContributedCustomEditors extends Disposable { + + private readonly _editors = new Map(); + + constructor() { + super(); + + customEditorsExtensionPoint.setHandler(extensions => { + this._editors.clear(); + + for (const extension of extensions) { + for (const webviewEditorContribution of extension.value) { + 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: getPriorityFromContribution(webviewEditorContribution, extension.description), + })); + } + } + this._onChange.fire(); + }); + } + + private readonly _onChange = this._register(new Emitter()); + public readonly onChange = this._onChange.event; + + public [Symbol.iterator](): Iterator { + return this._editors.values(); + } + + public get(viewType: string): CustomEditorInfo | undefined { + return viewType === defaultCustomEditor.id + ? defaultCustomEditor + : this._editors.get(viewType); + } + + public getContributedEditors(resource: URI): readonly CustomEditorInfo[] { + return Array.from(this._editors.values()) + .filter(customEditor => customEditor.matches(resource)); + } + + private add(info: CustomEditorInfo): void { + if (info.id === defaultCustomEditor.id || this._editors.has(info.id)) { + console.error(`Custom editor with id '${info.id}' already registered`); + return; + } + this._editors.set(info.id, info); + } +} + +function getPriorityFromContribution( + contribution: ICustomEditorsExtensionPoint, + 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; + } +} diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index e7b613ef6fc..a50e7f14c9c 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -3,89 +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_HAS_CUSTOM_EDITORS = new RawContextKey('hasCustomEditors', false); +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 type CustomEditorEdit = number; - 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<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>; - readonly onApplyEdit: Event<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>; - readonly onDisposeEdits: Event<{ edits: readonly CustomEditorEdit[] }>; + isReadonly(): boolean; - readonly onWillSave: Event; - readonly onWillSaveAs: Event; + isDirty(): boolean; + readonly onDidChangeDirty: Event; - onBackup(f: () => CancelablePromise): void; + revert(options?: IRevertOptions): Promise; - undo(): void; - redo(): void; - revert(options?: IRevertOptions): Promise; - - save(options?: ISaveOptions): Promise; - saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; - - pushEdit(edit: CustomEditorEdit, trigger: any): void; + saveCustomEditor(options?: ISaveOptions): Promise; + saveCustomEditorAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; } export const enum CustomEditorPriority { @@ -102,17 +76,20 @@ export class CustomEditorInfo { public readonly id: string; public readonly displayName: string; + public readonly providerDisplayName: string; public readonly priority: CustomEditorPriority; public readonly selector: readonly CustomEditorSelector[]; constructor(descriptor: { readonly id: string; readonly displayName: string; + readonly providerDisplayName: string; 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; } @@ -123,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; } } @@ -148,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 6448fbf2a63..00000000000 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.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 { 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 { CustomEditorEdit, 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 _currentEditIndex: number = -1; - private _savePoint: number = -1; - private readonly _edits: Array = []; - private _hotExitState: HotExitState.State = HotExitState.NotSupported; - - constructor( - public readonly viewType: string, - private readonly _resource: URI, - private readonly labelService: ILabelService, - ) { - super(); - } - - dispose() { - this._onDisposeEdits.fire({ edits: this._edits }); - super.dispose(); - } - - //#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._edits.length > 0 && this._savePoint !== this._currentEditIndex; - } - - protected readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); - readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; - - protected readonly _onDidChangeContent: Emitter = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; - - //#endregion - - protected readonly _onUndo = this._register(new Emitter<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>()); - readonly onUndo = this._onUndo.event; - - protected readonly _onApplyEdit = this._register(new Emitter<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>()); - readonly onApplyEdit = this._onApplyEdit.event; - - protected readonly _onDisposeEdits = this._register(new Emitter<{ edits: readonly CustomEditorEdit[] }>()); - readonly onDisposeEdits = this._onDisposeEdits.event; - - protected readonly _onWillSave = this._register(new Emitter()); - readonly onWillSave = this._onWillSave.event; - - protected readonly _onWillSaveAs = this._register(new Emitter()); - 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 pushEdit(edit: CustomEditorEdit, trigger: any) { - this.spliceEdits(edit); - - this._currentEditIndex = this._edits.length - 1; - this.updateDirty(); - this._onApplyEdit.fire({ edits: [edit], trigger }); - this.updateContentChanged(); - } - - private spliceEdits(editToInsert?: CustomEditorEdit) { - const start = this._currentEditIndex + 1; - const toRemove = this._edits.length - this._currentEditIndex; - - const removedEdits = editToInsert - ? this._edits.splice(start, toRemove, editToInsert) - : this._edits.splice(start, toRemove); - - if (removedEdits.length) { - this._onDisposeEdits.fire({ edits: removedEdits }); - } - } - - private updateDirty() { - // TODO@matt this should to be more fine grained and avoid - // emitting events if there was no change actually - this._onDidChangeDirty.fire(); - } - - private updateContentChanged() { - // TODO@matt revisit that this method is being called correctly - // on each case of content change within the custom editor - this._onDidChangeContent.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._savePoint = this._currentEditIndex; - this.updateDirty(); - - 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._savePoint = this._currentEditIndex; - this.updateDirty(); - - return true; - } - - public async revert(_options?: IRevertOptions) { - if (this._currentEditIndex === this._savePoint) { - return true; - } - - if (this._currentEditIndex >= this._savePoint) { - const editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex); - this._onUndo.fire({ edits: editsToUndo.reverse(), trigger: undefined }); - } else if (this._currentEditIndex < this._savePoint) { - const editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); - this._onApplyEdit.fire({ edits: editsToRedo, trigger: undefined }); - } - - this._currentEditIndex = this._savePoint; - this.spliceEdits(); - - this.updateDirty(); - this.updateContentChanged(); - return true; - } - - public undo() { - if (this._currentEditIndex < 0) { - // nothing to undo - return; - } - - const undoneEdit = this._edits[this._currentEditIndex]; - --this._currentEditIndex; - this._onUndo.fire({ edits: [undoneEdit], trigger: undefined }); - - this.updateDirty(); - this.updateContentChanged(); - } - - public redo() { - if (this._currentEditIndex >= this._edits.length - 1) { - // nothing to redo - return; - } - - ++this._currentEditIndex; - const redoneEdit = this._edits[this._currentEditIndex]; - - this._onApplyEdit.fire({ edits: [redoneEdit], trigger: undefined }); - - this.updateDirty(); - this.updateContentChanged(); - } - - 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 { - this._hotExitState = await pendingState.operation ? HotExitState.Allowed : HotExitState.NotAllowed; - } 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/customEditor/browser/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts similarity index 64% rename from src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts rename to src/vs/workbench/contrib/customEditor/common/extensionPoint.ts index 2a126dabefe..29b23af79c7 100644 --- a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/common/extensionPoint.ts @@ -9,45 +9,58 @@ import { CustomEditorPriority, CustomEditorSelector } from 'vs/workbench/contrib import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; -namespace WebviewEditorContribution { +namespace Fields { export const viewType = 'viewType'; export const displayName = 'displayName'; export const selector = 'selector'; export const priority = 'priority'; } -interface IWebviewEditorsExtensionPoint { - readonly [WebviewEditorContribution.viewType]: string; - readonly [WebviewEditorContribution.displayName]: string; - readonly [WebviewEditorContribution.selector]?: readonly CustomEditorSelector[]; - readonly [WebviewEditorContribution.priority]?: CustomEditorPriority; +export interface ICustomEditorsExtensionPoint { + readonly [Fields.viewType]: string; + readonly [Fields.displayName]: string; + readonly [Fields.selector]?: readonly CustomEditorSelector[]; + readonly [Fields.priority]?: string; } -const webviewEditorsContribution: IJSONSchema = { - description: nls.localize('contributes.webviewEditors', 'Contributes webview editors.'), +const CustomEditorsContribution: IJSONSchema = { + description: nls.localize('contributes.customEditors', 'Contributed custom editors.'), type: 'array', - defaultSnippets: [{ body: [{ viewType: '', displayName: '' }] }], + defaultSnippets: [{ + body: [{ + [Fields.viewType]: '$1', + [Fields.displayName]: '$2', + [Fields.selector]: [{ + filenamePattern: '$3' + }], + }] + }], items: { type: 'object', required: [ - WebviewEditorContribution.viewType, - WebviewEditorContribution.displayName, - WebviewEditorContribution.selector, + Fields.viewType, + Fields.displayName, + Fields.selector, ], properties: { - [WebviewEditorContribution.viewType]: { + [Fields.viewType]: { type: 'string', description: nls.localize('contributes.viewType', 'Unique identifier of the custom editor.'), }, - [WebviewEditorContribution.displayName]: { + [Fields.displayName]: { type: 'string', description: nls.localize('contributes.displayName', 'Human readable name of the custom editor. This is displayed to users when selecting which editor to use.'), }, - [WebviewEditorContribution.selector]: { + [Fields.selector]: { type: 'array', 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', @@ -56,18 +69,16 @@ const webviewEditorsContribution: IJSONSchema = { } } }, - [WebviewEditorContribution.priority]: { + [Fields.priority]: { type: 'string', description: nls.localize('contributes.priority', 'Controls when the custom editor is used. May be overridden by users.'), 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' } @@ -75,8 +86,8 @@ const webviewEditorsContribution: IJSONSchema = { } }; -export const webviewEditorsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'webviewEditors', +export const customEditorsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'customEditors', deps: [languagesExtPoint], - jsonSchema: webviewEditorsContribution + jsonSchema: CustomEditorsContribution }); 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 d64f9b7134e..98b766857a7 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,13 +28,16 @@ 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'; 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'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; const $ = dom.$; @@ -48,14 +51,15 @@ function createCheckbox(): HTMLInputElement { } const MAX_VISIBLE_BREAKPOINTS = 9; -export function getExpandedBodySize(model: IDebugModel): number { +export function getExpandedBodySize(model: IDebugModel, countLimit: number): number { const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length; - return Math.min(MAX_VISIBLE_BREAKPOINTS, length) * 22; + return Math.min(countLimit, length) * 22; } +type BreakpointItem = IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IExceptionBreakpoint; export class BreakpointsView extends ViewPane { - private list!: WorkbenchList; + private list!: WorkbenchList; private needsRefresh = false; constructor( @@ -71,10 +75,11 @@ export class BreakpointsView extends ViewPane { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + 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())); } @@ -85,7 +90,7 @@ export class BreakpointsView extends ViewPane { dom.addClass(container, 'debug-breakpoints'); const delegate = new BreakpointsDelegate(this.debugService); - this.list = >this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ + this.list = >this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ this.instantiationService.createInstance(BreakpointsRenderer), new ExceptionBreakpointsRenderer(this.debugService), this.instantiationService.createInstance(FunctionBreakpointsRenderer), @@ -95,12 +100,7 @@ export class BreakpointsView extends ViewPane { identityProvider: { getId: (element: IEnablement) => element.getId() }, multipleSelectionSupport: false, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IEnablement) => e }, - ariaProvider: { - getSetSize: (_: IEnablement, index: number, listLength: number) => listLength, - getPosInSet: (_: IEnablement, index: number) => index, - getRole: (breakpoint: IEnablement) => 'checkbox', - isChecked: (breakpoint: IEnablement) => breakpoint.enabled - }, + accessibilityProvider: new BreakpointsAccessibilityProvider(this.debugService), overrideStyles: { listBackground: this.getBackgroundColor() } @@ -225,12 +225,15 @@ export class BreakpointsView extends ViewPane { ]; } + private updateSize(): void { + // Adjust expanded body size + this.minimumBodySize = this.orientation === Orientation.VERTICAL ? getExpandedBodySize(this.debugService.getModel(), MAX_VISIBLE_BREAKPOINTS) : 170; + this.maximumBodySize = this.orientation === Orientation.VERTICAL ? getExpandedBodySize(this.debugService.getModel(), Number.POSITIVE_INFINITY) : 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; @@ -240,25 +243,25 @@ export class BreakpointsView extends ViewPane { } } - private get elements(): IEnablement[] { + private get elements(): BreakpointItem[] { const model = this.debugService.getModel(); const elements = (>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getDataBreakpoints()).concat(model.getBreakpoints()); - return elements; + return elements as BreakpointItem[]; } } -class BreakpointsDelegate implements IListVirtualDelegate { +class BreakpointsDelegate implements IListVirtualDelegate { constructor(private debugService: IDebugService) { // noop } - getHeight(element: IEnablement): number { + getHeight(_element: BreakpointItem): number { return 22; } - getTemplateId(element: IEnablement): string { + getTemplateId(element: BreakpointItem): string { if (element instanceof Breakpoint) { return BreakpointsRenderer.ID; } @@ -285,7 +288,7 @@ interface IBaseBreakpointTemplateData { breakpoint: HTMLElement; name: HTMLElement; checkbox: HTMLInputElement; - context: IEnablement; + context: BreakpointItem; toDispose: IDisposable[]; } @@ -615,7 +618,31 @@ class FunctionBreakpointInputRenderer implements IListRenderer { +class BreakpointsAccessibilityProvider implements IListAccessibilityProvider { + + constructor(private readonly debugService: IDebugService) { } + + getRole() { + return 'checkbox'; + } + + isChecked(breakpoint: IEnablement) { + return breakpoint.enabled; + } + + getAriaLabel(element: BreakpointItem): string | null { + if (element instanceof ExceptionBreakpoint) { + return element.toString(); + } + + const { message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), element as IBreakpoint | IDataBreakpoint | IFunctionBreakpoint); + const toString = element.toString(); + + return message ? `${toString} ${message}` : toString; + } +} + +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 5ae2c952efe..429193f2f04 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -18,9 +18,9 @@ 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 { IListAccessibilityProvider } 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'; @@ -28,15 +28,18 @@ import { TreeResourceNavigator, WorkbenchAsyncDataTree } from 'vs/platform/list/ 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.$; @@ -101,8 +104,9 @@ export class CallStackView extends ViewPane { @IContextKeyService readonly contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @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); + 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); @@ -129,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; @@ -168,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"), @@ -406,6 +414,7 @@ interface IErrorTemplateData { interface ILabelTemplateData { label: HTMLElement; + toDispose: IDisposable; } interface IStackFrameTemplateData { @@ -431,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 { @@ -607,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 { @@ -634,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; } @@ -779,7 +811,7 @@ class CallStackDataSource implements IAsyncDataSource { +class CallStackAccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: CallStackItem): string { if (element instanceof Thread) { return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (element).name); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 264fa2a775b..7e32a089e8c 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!../browser/media/debug.contribution'; -import 'vs/css!../browser/media/debugHover'; +import 'vs/css!./media/debug.contribution'; +import 'vs/css!./media/debugHover'; import * as nls from 'vs/nls'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; @@ -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 } from 'vs/platform/contextkey/common/contextkey'; +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,34 +113,35 @@ 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); registry.registerWorkbenchAction(SyncActionDescriptor.create(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory); -const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { +const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { when, command: { @@ -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); @@ -250,6 +254,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('debug.console.wordWrap', "Controls if the lines should wrap in the debug console."), default: true }, + 'debug.console.historySuggestions': { + type: 'boolean', + description: nls.localize('debug.console.historySuggestions', "Controls if the debug console should suggest previously typed input."), + default: true + }, 'launch': { type: 'object', description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces."), @@ -262,7 +271,7 @@ configurationRegistry.registerConfiguration({ default: true }, 'debug.onTaskErrors': { - enum: ['debugAnyway', 'showErrors', 'prompt', 'cancel'], + enum: ['debugAnyway', 'showErrors', 'prompt', 'abort'], enumDescriptions: [nls.localize('debugAnyway', "Ignore task errors and start debugging."), nls.localize('showErrors', "Show the Problems view and do not start debugging."), nls.localize('prompt', "Prompt user."), nls.localize('cancel', "Cancel debugging.")], description: nls.localize('debug.onTaskErrors', "Controls what to do when errors are encountered after running a preLaunchTask."), default: 'prompt' @@ -282,10 +291,11 @@ configurationRegistry.registerConfiguration({ // Register Debug Status Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugStatusContribution, LifecyclePhase.Eventually); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugProgressContribution, LifecyclePhase.Eventually); // Debug toolbar -const registerDebugToolBarItem = (id: string, title: string, order: number, icon: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { +const registerDebugToolBarItem = (id: string, title: string, order: number, icon: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { group: 'navigation', when, @@ -311,7 +321,7 @@ registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back" registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, { id: 'codicon/debug-reverse-continue' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); // Debug callstack context menu -const registerDebugCallstackItem = (id: string, title: string, order: number, when?: ContextKeyExpr, precondition?: ContextKeyExpr, group = 'navigation') => { +const registerDebugCallstackItem = (id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation') => { MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, { group, when, @@ -353,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 @@ -553,7 +563,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { // Touch Bar if (isMacintosh) { - const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpr | undefined, iconUri: URI) => { + const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { command: { id, 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 405b81daeed..aae1cb7919b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -11,9 +11,8 @@ import * as objects from 'vs/base/common/objects'; 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 * as editorCommon from 'vs/editor/common/editorCommon'; 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'; @@ -163,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; } @@ -338,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; @@ -434,15 +442,8 @@ export class ConfigurationManager implements IConfigurationManager { return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop(); } - getDebuggerLabelsForEditor(editor: editorCommon.IEditor | undefined): string[] { - if (isCodeEditor(editor)) { - const model = editor.getModel(); - const language = model ? model.getLanguageIdentifier().language : undefined; - - return this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0).map(d => d.label); - } - - return []; + isDebuggerInterestedInLanguage(language: string): boolean { + return this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0).length > 0; } async guessDebugger(type?: string): Promise { @@ -451,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) { @@ -522,23 +523,24 @@ 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 []; } else { - const names: string[] = []; + const configurations: (IConfig | ICompound)[] = []; if (config.configurations) { - names.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name)); - } - if (includeCompounds && config.compounds) { - if (config.compounds) { - names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length) - .map(compound => compound.name)); - } + configurations.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string')); } - return names; + 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); } } @@ -582,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 = ''; @@ -662,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!, @@ -705,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/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index bb510b66fd7..ff8715dd170 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -24,7 +24,7 @@ import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { coalesce } from 'vs/base/common/arrays'; @@ -109,6 +109,7 @@ export class DebugHoverWidget implements IContentWidget { accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, horizontalScrolling: true, + useShadows: false, overrideStyles: { listBackground: editorHoverBackground } @@ -176,7 +177,6 @@ export class DebugHoverWidget implements IContentWidget { } async showAt(range: Range, focus: boolean): Promise { - const session = this.debugService.getViewModel().focusedSession; if (!session || !this.editor.hasModel()) { @@ -208,7 +208,7 @@ export class DebugHoverWidget implements IContentWidget { if (!matchingExpression) { const lineContent = model.getLineContent(pos.lineNumber); - matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn); + matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn - 1); } } @@ -284,12 +284,12 @@ export class DebugHoverWidget implements IContentWidget { this.valueContainer.hidden = true; this.complexValueContainer.hidden = false; - await this.tree.setInput(expression); this.complexValueTitle.textContent = expression.value; this.complexValueTitle.title = expression.value; this.layoutTreeAndContainer(); this.editor.layoutContentWidget(this); this.scrollbar.scanDomNode(); + await this.tree.setInput(expression); this.tree.scrollTop = 0; this.tree.scrollLeft = 0; @@ -335,7 +335,7 @@ export class DebugHoverWidget implements IContentWidget { } } -class DebugHoverAccessibilityProvider implements IAccessibilityProvider { +class DebugHoverAccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: IExpression): string { return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", element.name, element.value); } 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 30a87cca33c..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( @@ -117,7 +118,6 @@ export class DebugService implements IDebugService { this.model = new DebugModel(this.loadBreakpoints(), this.loadFunctionBreakpoints(), this.loadExceptionBreakpoints(), this.loadDataBreakpoints(), this.loadWatchExpressions(), this.textFileService); - this.toDispose.push(this.model); const setBreakpointsExistContext = () => this.breakpointsExist.set(!!(this.model.getBreakpoints().length || this.model.getDataBreakpoints().length || this.model.getFunctionBreakpoints().length)); this.breakpointsExist = CONTEXT_BREAKPOINTS_EXIST.bindTo(contextKeyService); setBreakpointsExistContext(); @@ -125,8 +125,8 @@ export class DebugService implements IDebugService { this.viewModel = new ViewModel(contextKeyService); this.taskRunner = this.instantiationService.createInstance(DebugTaskRunner); - this.toDispose.push(this.fileService.onFileChanges(e => this.onFileChanges(e))); - this.lifecycleService.onShutdown(this.dispose, this); + this.toDispose.push(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); + this.toDispose.push(this.lifecycleService.onShutdown(this.dispose, this)); this.toDispose.push(this.extensionHostDebugService.onAttachSession(event => { const session = this.model.getSession(event.sessionId, true); @@ -207,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) { @@ -381,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 { @@ -392,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; } @@ -424,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) { @@ -433,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; @@ -451,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); @@ -522,7 +534,6 @@ export class DebugService implements IDebugService { await this.focusStackFrame(undefined, undefined, session); } } catch (err) { - session.shutdown(); if (this.viewModel.focusedSession === session) { await this.focusStackFrame(undefined); } @@ -567,8 +578,8 @@ export class DebugService implements IDebugService { this.notificationService.error(err); } } - session.shutdown(); this.endInitializingState(); + this.cancelTokens(session.getId()); this._onDidEndSession.fire(session); const focusedSession = this.viewModel.focusedSession; @@ -659,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; @@ -698,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())); @@ -747,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 11c7ef8329c..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'; @@ -19,7 +18,7 @@ import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSess import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, Queue } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; @@ -33,10 +32,12 @@ import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variables import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; 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; @@ -54,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(); @@ -61,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, @@ -74,16 +79,24 @@ export class DebugSession implements IDebugSession { @IProductService private readonly productService: IProductService, @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, @IOpenerService private readonly openerService: IOpenerService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, + @ILifecycleService lifecycleService: ILifecycleService ) { - this.id = generateUuid(); this._options = options || {}; if (this.hasSeparateRepl()) { this.repl = new ReplModel(); } else { this.repl = (this.parentSession as DebugSession).repl; } - this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire()); + + const toDispose: IDisposable[] = []; + toDispose.push(this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire())); + if (lifecycleService) { + toDispose.push(lifecycleService.onShutdown(() => { + this.shutdown(); + dispose(toDispose); + })); + } } getId(): string { @@ -175,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 /** @@ -204,15 +229,17 @@ 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(); + this.shutdown(); throw err; } } @@ -222,13 +249,20 @@ 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...) config.__sessionId = this.getId(); - await this.raw.launchOrAttach(config); - + try { + await this.raw.launchOrAttach(config); + } catch (err) { + this.shutdown(); + throw err; + } } /** @@ -236,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(); @@ -252,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(); @@ -264,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(); @@ -273,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) { @@ -307,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) { @@ -324,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) { @@ -334,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 }); @@ -346,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) { @@ -363,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); @@ -379,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); @@ -387,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); @@ -396,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 }); @@ -414,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); @@ -423,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; @@ -432,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 }); @@ -440,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); @@ -448,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 }); @@ -456,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 }); @@ -464,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 }); @@ -472,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 }); @@ -480,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 }); @@ -488,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 }); @@ -496,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 }); @@ -504,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 }); @@ -512,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 }); @@ -520,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 }); @@ -528,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 }); @@ -536,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); @@ -554,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({}); @@ -567,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({ @@ -578,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 { @@ -686,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 { @@ -768,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) { @@ -792,48 +834,58 @@ export class DebugSession implements IDebugSession { this._onDidChangeState.fire(); })); - let outpuPromises: Promise[] = []; + const outputQueue = new Queue(); this.rawListeners.push(this.raw.onDidOutput(async event => { - if (!event.body || !this.raw) { - return; - } - - const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info; - if (event.body.category === 'telemetry') { - // only log telemetry events from debug adapter if the debug extension provided the telemetry key - // and the user opted in telemetry - if (this.raw.customTelemetryService && this.telemetryService.isOptedIn) { - // __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly. - this.raw.customTelemetryService.publicLog(event.body.output, event.body.data); + outputQueue.queue(async () => { + if (!event.body || !this.raw) { + return; } - return; - } + const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info; + if (event.body.category === 'telemetry') { + // only log telemetry events from debug adapter if the debug extension provided the telemetry key + // and the user opted in telemetry + if (this.raw.customTelemetryService && this.telemetryService.isOptedIn) { + // __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly. + this.raw.customTelemetryService.publicLog(event.body.output, event.body.data); + } - // Make sure to append output in the correct order by properly waiting on preivous promises #33822 - const waitFor = outpuPromises.slice(); - const source = event.body.source && event.body.line ? { - lineNumber: event.body.line, - column: event.body.column ? event.body.column : 1, - source: this.getSource(event.body.source) - } : undefined; - if (event.body.variablesReference) { - const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid()); - outpuPromises.push(container.getChildren().then(async children => { - await Promise.all(waitFor); - children.forEach(child => { - // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) - (child).name = null; - this.appendToRepl(child, outputSeverity, source); + return; + } + + // Make sure to append output in the correct order by properly waiting on preivous promises #33822 + const source = event.body.source && event.body.line ? { + lineNumber: event.body.line, + column: event.body.column ? event.body.column : 1, + source: this.getSource(event.body.source) + } : undefined; + + if (event.body.group === 'start' || event.body.group === 'startCollapsed') { + const expanded = event.body.group === 'start'; + this.repl.startGroup(event.body.output || '', expanded, source); + return; + } + if (event.body.group === 'end') { + this.repl.endGroup(); + if (!event.body.output) { + // Only return if the end event does not have additional output in it + return; + } + } + + if (event.body.variablesReference) { + const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid()); + await container.getChildren().then(children => { + children.forEach(child => { + // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) + (child).name = null; + this.appendToRepl(child, outputSeverity, source); + }); }); - })); - } else if (typeof event.body.output === 'string') { - await Promise.all(waitFor); - this.appendToRepl(event.body.output, outputSeverity, source); - } - - await Promise.all(outpuPromises); - outpuPromises = []; + } else if (typeof event.body.output === 'string') { + this.appendToRepl(event.body.output, outputSeverity, source); + } + }); })); this.rawListeners.push(this.raw.onDidBreakpoint(event => { @@ -889,20 +941,32 @@ 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); + this.shutdown(); this._onDidEndAdapter.fire(event); })); } - shutdown(): void { + // Disconnects and clears state. Session can be initialized again for a new connection. + private shutdown(): void { dispose(this.rawListeners); if (this.raw) { this.raw.disconnect(); this.raw.dispose(); + this.raw = undefined; } - this.raw = undefined; this.fetchThreadsScheduler = undefined; this.model.clearThreads(this.getId(), true); this._onDidChangeState.fire(); 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/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index ff264cc8c29..4319d66031c 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -7,12 +7,12 @@ import * as osPath from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; 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'; const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f'; const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug'); @@ -38,7 +38,7 @@ export class LinkDetector { @IEditorService private readonly editorService: IEditorService, @IFileService private readonly fileService: IFileService, @IOpenerService private readonly openerService: IOpenerService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IRemotePathService private readonly remotePathService: IRemotePathService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { // noop @@ -120,7 +120,10 @@ export class LinkDetector { } if (path[0] === '~') { - path = osPath.join(this.environmentService.userHome, path.substring(1)); + const userHome = this.remotePathService.userHomeSync; + if (userHome) { + path = osPath.join(userHome.fsPath, path.substring(1)); + } } const link = this.createLink(text); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index ceb03abfcc9..286099ff89a 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'; @@ -17,7 +17,6 @@ import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from ' import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { tildify } from 'vs/base/common/labels'; import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -27,7 +26,7 @@ import { ResourceLabels, IResourceLabelProps, IResourceLabelOptions, IResourceLa import { FileKind } from 'vs/platform/files/common/files'; 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 { IListAccessibilityProvider } 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 { dispose } from 'vs/base/common/lifecycle'; @@ -39,6 +38,8 @@ import type { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTr import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; const NEW_STYLE_COMPRESS = true; @@ -240,12 +241,12 @@ class RootFolderTreeItem extends BaseTreeItem { class RootTreeItem extends BaseTreeItem { - constructor(private _environmentService: IEnvironmentService, private _contextService: IWorkspaceContextService, private _labelService: ILabelService) { + constructor(private _remotePathService: IRemotePathService, private _contextService: IWorkspaceContextService, private _labelService: ILabelService) { super(undefined, 'Root'); } add(session: IDebugSession): SessionTreeItem { - return this.createIfNeeded(session.getId(), () => new SessionTreeItem(this._labelService, this, session, this._environmentService, this._contextService)); + return this.createIfNeeded(session.getId(), () => new SessionTreeItem(this._labelService, this, session, this._remotePathService, this._contextService)); } find(session: IDebugSession): SessionTreeItem { @@ -261,7 +262,7 @@ class SessionTreeItem extends BaseTreeItem { private _map = new Map(); private _labelService: ILabelService; - constructor(labelService: ILabelService, parent: BaseTreeItem, session: IDebugSession, private _environmentService: IEnvironmentService, private rootProvider: IWorkspaceContextService) { + constructor(labelService: ILabelService, parent: BaseTreeItem, session: IDebugSession, private _remotePathService: IRemotePathService, private rootProvider: IWorkspaceContextService) { super(parent, session.getLabel(), true); this._labelService = labelService; this._session = session; @@ -346,8 +347,9 @@ class SessionTreeItem extends BaseTreeItem { } else { // on unix try to tildify absolute paths path = normalize(path); - if (!isWindows) { - path = tildify(path, this._environmentService.userHome); + const userHome = this._remotePathService.userHomeSync; + if (userHome && !isWindows) { + path = tildify(path, userHome.fsPath); } } } @@ -422,13 +424,14 @@ export class LoadedScriptsView extends ViewPane { @IEditorService private readonly editorService: IEditorService, @IContextKeyService readonly contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService, + @IRemotePathService private readonly remotePathService: IRemotePathService, @IOpenerService openerService: IOpenerService, @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); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } @@ -443,7 +446,7 @@ export class LoadedScriptsView extends ViewPane { this.filter = new LoadedScriptsFilter(); - const root = new RootTreeItem(this.environmentService, this.contextService, this.labelService); + const root = new RootTreeItem(this.remotePathService, this.contextService, this.labelService); this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.treeLabels); @@ -705,7 +708,7 @@ class LoadedScriptsRenderer implements ICompressibleTreeRenderer { +class LoadedSciptsAccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: LoadedScriptsItem): string { 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/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index b01a3d1193d..a0406499aa3 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -12,7 +12,6 @@ user-select: text; -webkit-user-select: text; word-break: break-all; - padding: 4px 5px; } .monaco-editor .debug-hover-widget .complex-value { @@ -62,6 +61,7 @@ overflow: auto; font-family: var(--monaco-monospace-font); max-height: 500px; + padding: 4px 5px; } .monaco-editor .debug-hover-widget .error { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 2408b52df02..33ada77a166 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -13,31 +13,6 @@ height: 100%; } -.debug-pane .debug-start-view { - padding: 0 20px 0 20px; -} - -.debug-pane .debug-start-view .monaco-button, -.debug-pane .debug-start-view .section { - margin-top: 20px; -} - -.debug-pane .debug-start-view .top-section { - margin-top: 10px; -} - -.debug-pane .debug-start-view .monaco-button { - max-width: 260px; - margin-left: auto; - margin-right: auto; - display: block; -} - -.debug-pane .debug-start-view .click { - cursor: pointer; - color: #007ACC; -} - .monaco-workbench .debug-action.notification:after { content: ''; width: 6px; @@ -108,6 +83,7 @@ .debug-pane .disabled { opacity: 0.65; + cursor: initial; } /* Call stack */ @@ -178,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; } @@ -256,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 { @@ -340,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..cdd67e12807 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 { @@ -75,7 +79,8 @@ } .repl .repl-input-wrapper { - padding-left: 20px; + display: flex; + align-items: center; border-top: 1px solid rgba(128, 128, 128, 0.35); } @@ -88,15 +93,14 @@ border-top-color: #6FC3DF; } -.repl .repl-input-wrapper:before { - left: 8px; - position: absolute; - content: '\276f'; - line-height: 18px; -} - -.monaco-workbench.linux .repl .repl-input-wrapper:before { - font-size: 9px; +.repl .repl-input-wrapper .repl-input-chevron { + padding: 0 6px 0 8px; + width: 16px; + height: 100%; + display: flex; + flex-shrink: 0; + justify-content: center; + font-weight: 600; } /* Output coloring and styling */ diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index da729176ce9..09814f70714 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 }); + 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); @@ -690,7 +706,7 @@ export class RawDebugSession implements IDisposable { The message is sent in the name of the adapter but the adapter doesn't know about it. However, since adapters are an open-ended set, we can not declared the events statically either. */ - this.customTelemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage }); + this.customTelemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage }, true); } } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 7ec2346bca7..584cfebe839 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!vs/workbench/contrib/debug/browser/media/repl'; +import 'vs/css!./media/repl'; import { URI as uri } from 'vs/base/common/uri'; import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; @@ -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,13 +50,15 @@ 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 } from 'vs/workbench/contrib/debug/browser/replViewer'; +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'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions'; const $ = dom.$; @@ -74,7 +76,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { _serviceBrand: undefined; 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; @@ -99,7 +101,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @IStorageService private readonly storageService: IStorageService, @IThemeService themeService: IThemeService, @IModelService private readonly modelService: IModelService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @ICodeEditorService codeEditorService: ICodeEditorService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextMenuService contextMenuService: IContextMenuService, @@ -109,8 +111,9 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @IEditorService private readonly editorService: IEditorService, @IKeybindingService keybindingService: IKeybindingService, @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); + 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, {}); @@ -145,26 +148,39 @@ 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 }); } }); } - const history = this.history.getHistory(); - history.forEach(h => suggestions.push({ - label: h, - insertText: h, - kind: CompletionItemKind.Text, - range: computeRange(h.length), - sortText: 'ZZZ' - })); + if (this.configurationService.getValue('debug').console.historySuggestions) { + const history = this.history.getHistory(); + history.forEach(h => suggestions.push({ + label: h, + insertText: h, + kind: CompletionItemKind.Text, + range: computeRange(h.length), + sortText: 'ZZZ' + })); + } return { suggestions }; } @@ -185,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(); @@ -195,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(); @@ -204,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(); })); @@ -239,24 +266,33 @@ 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 + this.replInput.updateOptions({ + fontSize, + lineHeight: debugConsole.lineHeight, + fontFamily: debugConsole.fontFamily === 'default' ? EDITOR_FONT_DEFAULTS.fontFamily : debugConsole.fontFamily + }); + + const replInputLineHeight = this.replInput.getOption(EditorOption.lineHeight); + + // 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; @@ -270,9 +306,21 @@ 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 .repl-input-chevron { + line-height: ${replInputLineHeight}px + } + + .repl .repl-input-wrapper .monaco-editor .lines-content { + background-color: ${backgroundColor}; + } `; this.tree.rerender(); + + if (this.dimension) { + this.layoutBody(this.dimension.height, this.dimension.width); + } } } @@ -364,7 +412,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { protected layoutBody(height: number, width: number): void { this.dimension = new dom.Dimension(width, height); - const replInputHeight = Repl.REPL_INPUT_LINE_HEIGHT * this.replInputLineCount; + const replInputHeight = Math.min(this.replInput.getContentHeight(), height); if (this.tree) { const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; const treeHeight = height - replInputHeight; @@ -376,7 +424,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { } this.replInputContainer.style.height = `${replInputHeight}px`; - this.replInput.layout({ width: width - 20, height: replInputHeight }); + this.replInput.layout({ width: width - 30, height: replInputHeight }); } focus(): void { @@ -416,6 +464,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; @@ -423,6 +472,27 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; await this.tree.updateChildren(); + + const session = this.tree.getInput(); + if (session) { + // 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) { // Only scroll if we were scrolled all the way down before tree refreshed #10486 revealLastElement(this.tree); @@ -452,6 +522,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { this.instantiationService.createInstance(ReplVariablesRenderer, linkDetector), this.instantiationService.createInstance(ReplSimpleElementsRenderer, linkDetector), new ReplEvaluationInputsRenderer(), + new ReplGroupRenderer(), new ReplEvaluationResultsRenderer(linkDetector), new ReplRawObjectsRenderer(linkDetector), ], @@ -467,7 +538,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))); @@ -483,11 +554,12 @@ 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 { this.replInputContainer = dom.append(container, $('.repl-input-wrapper')); + dom.append(this.replInputContainer, $('.repl-input-chevron.codicon.codicon-chevron-right')); const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: this.replInputContainer, historyNavigator: this }); this.historyNavigationEnablement = historyNavigationEnablement; @@ -566,7 +638,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/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index de9cd9ce40b..8283d639a07 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -5,9 +5,9 @@ import severity from 'vs/base/common/severity'; import * as dom from 'vs/base/browser/dom'; -import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; -import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; +import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -30,6 +30,10 @@ interface IReplEvaluationInputTemplateData { label: HighlightedLabel; } +interface IReplGroupTemplateData { + label: HighlightedLabel; +} + interface IReplEvaluationResultTemplateData { value: HTMLElement; annotation: HTMLElement; @@ -76,6 +80,29 @@ export class ReplEvaluationInputsRenderer implements ITreeRenderer { + static readonly ID = 'replGroup'; + + get templateId(): string { + return ReplGroupRenderer.ID; + } + + renderTemplate(container: HTMLElement): IReplEvaluationInputTemplateData { + const input = dom.append(container, $('.expression')); + const label = new HighlightedLabel(input, false); + return { label }; + } + + renderElement(element: ITreeNode, _index: number, templateData: IReplGroupTemplateData): void { + const replGroup = element.element; + templateData.label.set(replGroup.name, createMatches(element.filterData)); + } + + disposeTemplate(_templateData: IReplEvaluationInputTemplateData): void { + // noop + } +} + export class ReplEvaluationResultsRenderer implements ITreeRenderer { static readonly ID = 'replEvaluationResult'; @@ -296,6 +323,9 @@ export class ReplDelegate extends CachedListVirtualDelegate { // Variable with no name is a top level variable which should be rendered like a repl element #17404 return ReplSimpleElementsRenderer.ID; } + if (element instanceof ReplGroup) { + return ReplGroupRenderer.ID; + } return ReplRawObjectsRenderer.ID; } @@ -317,7 +347,7 @@ export class ReplDataSource implements IAsyncDataSourceelement).hasChildren; + return !!(element).hasChildren; } getChildren(element: IReplElement | IDebugSession): Promise { @@ -327,12 +357,15 @@ export class ReplDataSource implements IAsyncDataSourceelement).getChildren(); } } -export class ReplAccessibilityProvider implements IAccessibilityProvider { +export class ReplAccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: IReplElement): string { if (element instanceof Variable) { return localize('replVariableAriaLabel', "Variable {0} has value {1}, read eval print loop, debug", element.name, element.value); @@ -343,6 +376,9 @@ export class ReplAccessibilityProvider implements IAccessibilityProvider any): HTMLSpanElement { - const clickElement = $('span.click'); - clickElement.textContent = textContent; - clickElement.onclick = action; - clickElement.tabIndex = 0; - clickElement.onkeyup = (e) => { - const keyboardEvent = new StandardKeyboardEvent(e); - if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) { - action(); - } - }; - - return clickElement; -} - -export class StartView extends ViewPane { - - static ID = 'workbench.debug.startView'; - static LABEL = localize('start', "Start"); - - private debugButton!: Button; - private firstMessageContainer!: HTMLElement; - private secondMessageContainer!: HTMLElement; - private clickElement: HTMLElement | undefined; - private debuggerLabels: string[] | undefined = undefined; - - constructor( - options: IViewletViewOptions, - @IThemeService themeService: IThemeService, - @IKeybindingService keybindingService: IKeybindingService, - @IContextMenuService contextMenuService: IContextMenuService, - @IConfigurationService configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService, - @ICommandService private readonly commandService: ICommandService, - @IDebugService private readonly debugService: IDebugService, - @IEditorService private readonly editorService: IEditorService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IFileDialogService private readonly dialogService: IFileDialogService, - @IInstantiationService instantiationService: IInstantiationService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IOpenerService openerService: IOpenerService, - ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); - this._register(editorService.onDidActiveEditorChange(() => this.updateView())); - this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView())); - } - - private updateView(): void { - const activeEditor = this.editorService.activeTextEditorWidget; - const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor); - if (!equals(this.debuggerLabels, debuggerLabels)) { - this.debuggerLabels = debuggerLabels; - const enabled = this.debuggerLabels.length > 0; - - this.debugButton.enabled = enabled; - const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); - let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Run and Debug") : localize('debugWith', "Run and Debug {0}", this.debuggerLabels[0]); - if (debugKeybinding) { - debugLabel += ` (${debugKeybinding.getLabel()})`; - } - this.debugButton.label = debugLabel; - - const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY; - this.firstMessageContainer.innerHTML = ''; - this.secondMessageContainer.innerHTML = ''; - const secondMessageElement = $('span'); - this.secondMessageContainer.appendChild(secondMessageElement); - - const setSecondMessage = () => { - secondMessageElement.textContent = localize('specifyHowToRun', "To customize Run and Debug"); - this.clickElement = createClickElement(localize('configure', " create a launch.json file."), () => { - this.telemetryService.publicLog2('debugStart.configure', { debuggers: this.debuggerLabels }); - this.commandService.executeCommand(ConfigureAction.ID); - }); - this.secondMessageContainer.appendChild(this.clickElement); - }; - const setSecondMessageWithFolder = () => { - secondMessageElement.textContent = localize('noLaunchConfiguration', "To customize Run and Debug, "); - this.clickElement = createClickElement(localize('openFolder', " open a folder"), () => { - this.telemetryService.publicLog2('debugStart.openFolder', { debuggers: this.debuggerLabels }); - this.dialogService.pickFolderAndOpen({ forceNewWindow: false }); - }); - this.secondMessageContainer.appendChild(this.clickElement); - - const moreText = $('span.moreText'); - moreText.textContent = localize('andconfigure', " and create a launch.json file."); - this.secondMessageContainer.appendChild(moreText); - }; - - if (enabled && !emptyWorkbench) { - setSecondMessage(); - } - - if (enabled && emptyWorkbench) { - setSecondMessageWithFolder(); - } - - if (!enabled && !emptyWorkbench) { - const firstMessageElement = $('span'); - this.firstMessageContainer.appendChild(firstMessageElement); - firstMessageElement.textContent = localize('simplyDebugAndRun', "Open a file which can be debugged or run."); - - setSecondMessage(); - } - - if (!enabled && emptyWorkbench) { - this.clickElement = createClickElement(localize('openFile', "Open a file"), () => { - this.telemetryService.publicLog2('debugStart.openFile'); - this.dialogService.pickFileAndOpen({ forceNewWindow: false }); - }); - this.firstMessageContainer.appendChild(this.clickElement); - const firstMessageElement = $('span'); - this.firstMessageContainer.appendChild(firstMessageElement); - firstMessageElement.textContent = localize('canBeDebuggedOrRun', " which can be debugged or run."); - - setSecondMessageWithFolder(); - } - } - } - - protected renderBody(container: HTMLElement): void { - super.renderBody(container); - - this.firstMessageContainer = $('.top-section'); - container.appendChild(this.firstMessageContainer); - - this.debugButton = new Button(container); - this._register(this.debugButton.onDidClick(() => { - this.commandService.executeCommand(StartAction.ID); - this.telemetryService.publicLog2('debugStart.runAndDebug', { debuggers: this.debuggerLabels }); - })); - attachButtonStyler(this.debugButton, this.themeService); - - dom.addClass(this.element, 'debug-pane'); - dom.addClass(container, 'debug-start-view'); - - this.secondMessageContainer = $('.section'); - container.appendChild(this.secondMessageContainer); - - this.updateView(); - } - - protected layoutBody(_: number, __: number): void { - // no-op - } - - focus(): void { - if (this.debugButton.enabled) { - this.debugButton.focus(); - } else if (this.clickElement) { - this.clickElement.focus(); - } - } -} 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 4dd9c4bfac0..488a1b871bc 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,8 +17,8 @@ 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 { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IListAccessibilityProvider } 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'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -33,6 +33,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const $ = dom.$; let forgetScopes = true; @@ -58,8 +59,9 @@ export class VariablesView extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); // Use scheduler to prevent unnecessary flashing this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { @@ -78,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); + } } } @@ -95,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(), @@ -215,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; } @@ -244,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; } @@ -276,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'; @@ -312,7 +348,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { } } -class VariablesAccessibilityProvider implements IAccessibilityProvider { +class VariablesAccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: IExpression | IScope): string | null { if (element instanceof Scope) { return nls.localize('variableScopeAriaLabel', "Scope {0}, variables, debug", element.name); diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 99b9cc6d32e..298934a4bc2 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -18,9 +18,9 @@ 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 { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; import { IDragAndDropData } from 'vs/base/browser/dnd'; @@ -33,6 +33,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreVariableSetEmitter = false; @@ -55,8 +56,9 @@ export class WatchExpressionsView extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @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); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; @@ -188,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, () => { @@ -202,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)); @@ -298,7 +296,7 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { } } -class WatchExpressionsAccessibilityProvider implements IAccessibilityProvider { +class WatchExpressionsAccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: IExpression): string { if (element instanceof Expression) { return nls.localize('watchExpressionAriaLabel', "{0} value {1}, watch, debug", (element).name, (element).value); diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts new file mode 100644 index 00000000000..74501598045 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +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 { 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'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { isMacintosh } from 'vs/base/common/platform'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +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 WelcomeView extends ViewPane { + + static ID = 'workbench.debug.welcome'; + static LABEL = localize('run', "Run"); + + private debugStartLanguageContext: IContextKey; + private debuggerInterestedContext: IContextKey; + + constructor( + options: IViewletViewOptions, + @IThemeService themeService: IThemeService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IDebugService private readonly debugService: IDebugService, + @IEditorService private readonly editorService: IEditorService, + @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IOpenerService openerService: IOpenerService, + @IStorageService storageSevice: IStorageService, + @ITelemetryService telemetryService: ITelemetryService, + ) { + 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); + const lastSetLanguage = storageSevice.get(debugStartLanguageKey, StorageScope.WORKSPACE); + this.debugStartLanguageContext.set(lastSetLanguage); + + const setContextKey = () => { + 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); + this.debuggerInterestedContext.set(true); + storageSevice.store(debugStartLanguageKey, language, StorageScope.WORKSPACE); + return; + } + } + this.debuggerInterestedContext.set(false); + }; + 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); + debugKeybindingLabel = debugKeybinding ? ` (${debugKeybinding.getLabel()})` : ''; + } + + shouldShowWelcome(): boolean { + return true; + } +} + +const viewsRegistry = Registry.as(Extensions.ViewsRegistry); +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(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(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(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 56424879c1c..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,9 +199,9 @@ export interface IDebugSession extends ITreeElement { readonly onDidLoadedSource: Event; readonly onDidCustomEvent: Event; - - // Disconnects and clears state. Session can be initialized again for a new connection. - shutdown(): void; + readonly onDidProgressStart: Event; + readonly onDidProgressUpdate: Event; + readonly onDidProgressEnd: Event; // DAP request @@ -224,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; @@ -304,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 { @@ -319,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; } @@ -465,6 +467,7 @@ export interface IDebugConfiguration { lineHeight: number; wordWrap: boolean; closeOnEnd: boolean; + historySuggestions: boolean; }; focusWindowOnBreak: boolean; onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt' | 'abort'; @@ -649,7 +652,7 @@ export interface IConfigurationManager { activateDebuggers(activationEvent: string, debugType?: string): Promise; - getDebuggerLabelsForEditor(editor: editorCommon.IEditor | undefined): string[]; + isDebuggerInterestedInLanguage(language: string): boolean; hasDebugConfigurationProvider(debugType: string): boolean; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; @@ -660,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; @@ -706,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 67de4eb4bee..e06b75b2d7a 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import { URI as uri } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; -import * as lifecycle from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { generateUuid } from 'vs/base/common/uuid'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -22,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 { @@ -270,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; @@ -291,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; @@ -348,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); } @@ -819,7 +843,6 @@ export class ThreadAndSessionIds implements ITreeElement { export class DebugModel implements IDebugModel { private sessions: IDebugSession[]; - private toDispose: lifecycle.IDisposable[]; private schedulers = new Map(); private breakpointsActivated = true; private readonly _onDidChangeBreakpoints = new Emitter(); @@ -835,7 +858,6 @@ export class DebugModel implements IDebugModel { private textFileService: ITextFileService ) { this.sessions = []; - this.toDispose = []; } getId(): string { @@ -1227,10 +1249,4 @@ export class DebugModel implements IDebugModel { }); this._onDidChangeCallStack.fire(undefined); } - - dispose(): void { - // Make sure to shutdown each session, such that no debugged process is left laying around - this.sessions.forEach(s => s.shutdown()); - this.toDispose = lifecycle.dispose(this.toDispose); - } } diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index e2d0602ef91..4307295e632 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + /** Declaration module describing the VS Code debug protocol. Auto-generated from json schema. Do not edit manually. */ @@ -69,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'; @@ -84,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. */ @@ -98,7 +109,7 @@ declare module DebugProtocol { 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 one 'setFunctionBreakpoints' request (if capability 'supportsFunctionBreakpoints' is true) - 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. @@ -201,6 +212,15 @@ declare module DebugProtocol { category?: string; /** The output to report. */ output: string; + /** Support for keeping an output log organized by grouping related messages. + 'start': Start a new group in expanded mode. Subsequent output events are members of the group and should be shown indented. + The 'output' attribute becomes the name of the group and is not indented. + 'startCollapsed': Start a new group in collapsed mode. Subsequent output events are members of the group and should be shown indented (as soon as the group is expanded). + The 'output' attribute becomes the name of the group and is not indented. + 'end': End the current group and decreases the indentation of subsequent output events. + A non empty 'output' attribute is shown as the unindented end of the group. + */ + group?: 'start' | 'startCollapsed' | 'end'; /** 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. */ @@ -292,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'; @@ -325,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 { @@ -360,6 +447,8 @@ declare module DebugProtocol { supportsRunInTerminalRequest?: boolean; /** Client supports memory references. */ supportsMemoryReferences?: boolean; + /** Client supports progress reporting. */ + supportsProgressReporting?: boolean; } /** Response to 'initialize' request. */ @@ -369,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'; @@ -385,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'; @@ -408,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'; @@ -429,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'; @@ -448,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'; @@ -461,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; } @@ -472,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'; @@ -490,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'; @@ -550,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[]; }; } @@ -559,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'; @@ -582,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'; @@ -593,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[]; } @@ -603,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'; @@ -613,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; } @@ -635,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'; @@ -667,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; }; } @@ -744,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'; @@ -762,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'; @@ -782,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'; @@ -803,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'; @@ -856,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; } @@ -913,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; } @@ -926,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'; @@ -952,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; }; @@ -977,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; } @@ -1008,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'; @@ -1025,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'; @@ -1052,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'; @@ -1090,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; } @@ -1102,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; }; } @@ -1124,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'; @@ -1147,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; }; @@ -1168,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'; @@ -1191,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'; @@ -1218,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'; @@ -1247,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'; @@ -1275,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'; @@ -1294,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; @@ -1305,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'; @@ -1319,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; @@ -1399,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. */ @@ -1467,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 { @@ -1498,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[]; @@ -1520,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; @@ -1538,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'; } @@ -1590,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; @@ -1606,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; } @@ -1623,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. */ @@ -1664,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; } @@ -1676,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; } @@ -1693,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; } @@ -1703,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; @@ -1713,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; } @@ -1763,6 +1966,16 @@ declare module DebugProtocol { If missing the value 0 is assumed which results in the completion text being inserted. */ length?: number; + /** Determines the start of the new selection after the text has been inserted (or replaced). + The start position must in the range 0 and length of the completion text. + If omitted the selection starts at the end of the completion text. + */ + selectionStart?: number; + /** Determines the length of the new selection after the text has been inserted (or replaced). + The selection can not extend beyond the bounds of the completion text. + If omitted the length is assumed to be 0. + */ + selectionLength?: number; } /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ @@ -1805,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; @@ -1819,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; @@ -1853,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(); @@ -162,11 +216,29 @@ export class ReplModel { } } - private addReplElement(newElement: IReplElement): void { - this.replElements.push(newElement); - if (this.replElements.length > MAX_REPL_LENGTH) { - this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH); + startGroup(name: string, autoExpand: boolean, sourceData?: IReplElementSource): void { + const group = new ReplGroup(name, autoExpand, sourceData); + this.addReplElement(group); + } + + endGroup(): void { + const lastElement = this.replElements[this.replElements.length - 1]; + if (lastElement instanceof ReplGroup) { + lastElement.end(); } + } + + private addReplElement(newElement: IReplElement): void { + const lastElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined; + if (lastElement instanceof ReplGroup && !lastElement.hasEnded) { + lastElement.addChild(newElement); + } else { + this.replElements.push(newElement); + if (this.replElements.length > MAX_REPL_LENGTH) { + this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH); + } + } + this._onDidChangeElements.fire(); } diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 6be7695e38f..26d3e336d32 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -12,7 +12,7 @@ import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfigurat let externalTerminalService: IExternalTerminalService | undefined = undefined; -export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): void { +export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): Promise { 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 31844187fbe..1d0321318ea 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -17,9 +17,11 @@ import { LanguageIdentifier, LanguageId } from 'vs/editor/common/modes'; import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; 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!); + 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 { @@ -96,10 +98,10 @@ suite('Debug - Breakpoints', () => { const modelUri1 = uri.file('/myfolder/my file first.js'); const modelUri2 = uri.file('/secondfolder/second/second file.js'); addBreakpointsAndCheckEvents(model, modelUri1, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]); - assert.equal(getExpandedBodySize(model), 44); + assert.equal(getExpandedBodySize(model, 9), 44); addBreakpointsAndCheckEvents(model, modelUri2, [{ lineNumber: 1, enabled: true }, { lineNumber: 2, enabled: true }, { lineNumber: 3, enabled: false }]); - assert.equal(getExpandedBodySize(model), 110); + assert.equal(getExpandedBodySize(model, 9), 110); assert.equal(model.getBreakpoints().length, 5); assert.equal(model.getBreakpoints({ uri: modelUri1 }).length, 2); @@ -133,7 +135,7 @@ suite('Debug - Breakpoints', () => { assert.equal(bp.enabled, true); model.removeBreakpoints(model.getBreakpoints({ uri: modelUri1 })); - assert.equal(getExpandedBodySize(model), 66); + assert.equal(getExpandedBodySize(model, 9), 66); assert.equal(model.getBreakpoints().length, 3); }); @@ -319,7 +321,7 @@ suite('Debug - Breakpoints', () => { test('decorations', () => { const modelUri = uri.file('/myfolder/my file first.js'); const languageIdentifier = new LanguageIdentifier('testMode', LanguageId.PlainText); - const textModel = new TextModel( + const textModel = createTextModel( ['this is line one', 'this is line two', ' this is line three it has whitespace at start', 'this is line four', 'this is line five'].join('\n'), TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier 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 558aa88393d..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!); + 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!); + }(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/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index 297496be064..1356848adc3 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import severity from 'vs/base/common/severity'; import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug'; -import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; +import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; import { timeout } from 'vs/base/common/async'; import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; @@ -151,4 +151,42 @@ suite('Debug - REPL', () => { assert.equal((session.getReplElements()[4]).value, '=after.2'); assert.equal((session.getReplElements()[5]).value, 'after.2'); }); + + test('repl groups', async () => { + const session = createMockSession(model); + const repl = new ReplModel(); + + repl.appendToRepl(session, 'first global line', severity.Info); + repl.startGroup('group_1', true); + repl.appendToRepl(session, 'first line in group', severity.Info); + repl.appendToRepl(session, 'second line in group', severity.Info); + const elements = repl.getReplElements(); + assert.equal(elements.length, 2); + const group = elements[1] as ReplGroup; + assert.equal(group.name, 'group_1'); + assert.equal(group.autoExpand, true); + assert.equal(group.hasChildren, true); + assert.equal(group.hasEnded, false); + + repl.startGroup('group_2', false); + repl.appendToRepl(session, 'first line in subgroup', severity.Info); + repl.appendToRepl(session, 'second line in subgroup', severity.Info); + const children = group.getChildren(); + assert.equal(children.length, 3); + assert.equal((children[0]).value, 'first line in group'); + assert.equal((children[1]).value, 'second line in group'); + assert.equal((children[2]).name, 'group_2'); + assert.equal((children[2]).hasEnded, false); + assert.equal((children[2]).getChildren().length, 2); + repl.endGroup(); + assert.equal((children[2]).hasEnded, true); + repl.appendToRepl(session, 'third line in group', severity.Info); + assert.equal(group.getChildren().length, 4); + assert.equal(group.hasEnded, false); + repl.endGroup(); + assert.equal(group.hasEnded, true); + repl.appendToRepl(session, 'second global line', severity.Info); + assert.equal(repl.getReplElements().length, 3); + assert.equal((repl.getReplElements()[2]).value, 'second global line'); + }); }); 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 14d4a539a74..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[] { @@ -327,8 +343,6 @@ export class MockSession implements IDebugSession { goto(threadId: number, targetId: number): Promise { throw new Error('Method not implemented.'); } - - shutdown(): void { } } export class MockRawSession { 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 6ba96158d52..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!); + 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 a1320015292..4bb02853442 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -21,6 +21,8 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; import { RunOnceWorker } from 'vs/base/common/async'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { equals } from 'vs/base/common/objects'; export const enum ExperimentState { Evaluating, @@ -49,15 +51,20 @@ export interface IExperimentActionPromptProperties { } export interface IExperimentActionPromptCommand { - text: string | { [key: string]: string }; + text: string | { [key: string]: string; }; 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; } @@ -81,32 +88,79 @@ interface IExperimentStorageState { lastEditedDate?: string; } +/** + * Current version of the experiment schema in this VS Code build. This *must* + * be incremented when adding a condition, otherwise experiments might activate + * on older versions of VS Code where not intended. + */ +export const currentSchemaVersion = 3; + interface IRawExperiment { id: string; + schemaVersion: number; enabled?: boolean; condition?: { insidersOnly?: boolean; newUser?: boolean; displayLanguage?: string; + // Evaluates to true iff all the given user settings are deeply equal + userSetting?: { [key: string]: unknown; }; + // Start the experiment if the number of activation events have happened over the last week: + activationEvent?: { + event: string; + uniqueDays?: number; + minEvents: number; + }; installedExtensions?: { excludes?: string[]; includes?: string[]; - }, + }; fileEdits?: { filePathPattern?: string; workspaceIncludes?: string[]; workspaceExcludes?: string[]; minEditCount: number; - }, + }; experimentsPreviouslyRun?: { excludes?: string[]; includes?: string[]; - } + }; userProbability?: number; }; action?: IExperimentAction; + action2?: IExperimentAction; } +interface IActivationEventRecord { + count: number[]; + mostRecentBucket: number; +} + +const experimentEventStorageKey = (event: string) => 'experimentEventRecord-' + event.replace(/[^0-9a-z]/ig, '-'); + +/** + * Updates the activation record to shift off days outside the window + * we're interested in. + */ +export const getCurrentActivationRecord = (previous?: IActivationEventRecord, dayWindow = 7): IActivationEventRecord => { + const oneDay = 1000 * 60 * 60 * 24; + const now = Date.now(); + if (!previous) { + return { count: new Array(dayWindow).fill(0), mostRecentBucket: now }; + } + + // get the number of days, up to dayWindow, that passed since the last bucket update + const shift = Math.min(dayWindow, Math.floor((now - previous.mostRecentBucket) / oneDay)); + if (!shift) { + return previous; + } + + return { + count: new Array(shift).fill(0).concat(previous.count.slice(0, -shift)), + mostRecentBucket: previous.mostRecentBucket + shift * oneDay, + }; +}; + export class ExperimentService extends Disposable implements IExperimentService { _serviceBrand: undefined; private _experiments: IExperiment[] = []; @@ -125,11 +179,13 @@ export class ExperimentService extends Disposable implements IExperimentService @IRequestService private readonly requestService: IRequestService, @IConfigurationService private readonly configurationService: IConfigurationService, @IProductService private readonly productService: IProductService, - @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService + @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, + @IExtensionService private readonly extensionService: IExtensionService ) { super(); - this._loadExperimentsPromise = Promise.resolve(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() => this.loadExperiments()); + this._loadExperimentsPromise = Promise.resolve(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() => + this.loadExperiments()); } public getExperimentById(id: string): Promise { @@ -178,8 +234,8 @@ export class ExperimentService extends Disposable implements IExperimentService if (context.res.statusCode !== 200) { return null; } - const result: any = await asJson(context); - return result && Array.isArray(result['experiments']) ? result['experiments'] : []; + const result = await asJson<{ experiments?: IRawExperiment; }>(context); + return result && Array.isArray(result.experiments) ? result.experiments : []; } catch (_e) { // Bad request or invalid JSON return null; @@ -198,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 }); @@ -207,6 +264,10 @@ export class ExperimentService extends Disposable implements IExperimentService return Promise.resolve(null); } + // Don't look at experiments with newer schema versions. We can't + // understand them, trying to process them might even cause errors. + rawExperiments = rawExperiments.filter(e => (e.schemaVersion || 0) <= currentSchemaVersion); + // Clear disbaled/deleted experiments from storage const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []); const enabledExperiments = rawExperiments.filter(experiment => !!experiment.enabled).map(experiment => experiment.id.toLowerCase()); @@ -223,66 +284,81 @@ export class ExperimentService extends Disposable implements IExperimentService this.storageService.remove('allExperiments', StorageScope.GLOBAL); } - 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; - } - }); + const activationEvents = new Set(rawExperiments.map(exp => exp.condition?.activationEvent?.event).filter(evt => !!evt)); + if (activationEvents.size) { + this._register(this.extensionService.onWillActivateByEvent(evt => { + if (activationEvents.has(evt.event)) { + this.recordActivatedEvent(evt.event); } - 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' }; + experiments: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; }; - this.telemetryService.publicLog2<{ experiments: IExperiment[] }, ExperimentsClassification>('experiments', { experiments: this._experiments }); + this.telemetryService.publicLog2<{ experiments: IExperiment[]; }, ExperimentsClassification>('experiments', { experiments: this._experiments }); }); }); } + 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), []); @@ -298,7 +374,7 @@ export class ExperimentService extends Disposable implements IExperimentService } private checkExperimentDependencies(experiment: IRawExperiment): boolean { - const experimentsPreviouslyRun = experiment.condition ? experiment.condition.experimentsPreviouslyRun : undefined; + const experimentsPreviouslyRun = experiment.condition?.experimentsPreviouslyRun; if (experimentsPreviouslyRun) { const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []); let includeCheck = true; @@ -318,6 +394,37 @@ export class ExperimentService extends Disposable implements IExperimentService return true; } + private recordActivatedEvent(event: string) { + const key = experimentEventStorageKey(event); + 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) { + const setting = experiment.condition?.activationEvent; + if (!setting) { + return true; + } + + const { count } = getCurrentActivationRecord(safeParse(this.storageService.get(experimentEventStorageKey(setting.event), StorageScope.GLOBAL), undefined)); + + let total = 0; + let uniqueDays = 0; + for (const entry of count) { + if (entry > 0) { + uniqueDays++; + total += entry; + } + } + + return total >= setting.minEvents && (!setting.uniqueDays || uniqueDays >= setting.uniqueDays); + } + private shouldRunExperiment(experiment: IRawExperiment, processedExperiment: IExperiment): Promise { if (processedExperiment.state !== ExperimentState.Evaluating) { return Promise.resolve(processedExperiment.state); @@ -336,6 +443,16 @@ export class ExperimentService extends Disposable implements IExperimentService return Promise.resolve(ExperimentState.NoRun); } + for (const [key, value] of Object.entries(experiment.condition?.userSetting || {})) { + if (!equals(this.configurationService.getValue(key), value)) { + return Promise.resolve(ExperimentState.NoRun); + } + } + + if (!this.checkActivationEventFrequency(experiment)) { + return Promise.resolve(ExperimentState.Evaluating); + } + if (this.productService.quality === 'stable' && condition.insidersOnly === true) { return Promise.resolve(ExperimentState.NoRun); } @@ -442,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 c19c6ec0085..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 @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; +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 } from 'vs/workbench/test/browser/workbenchTestServices'; import { @@ -27,6 +28,9 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; 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; @@ -34,7 +38,7 @@ interface ExperimentSettings { state?: ExperimentState; } -let experimentData: { [i: string]: any } = { +let experimentData: { [i: string]: any; } = { experiments: [] }; @@ -61,6 +65,7 @@ suite('Experiment Service', () => { let instantiationService: TestInstantiationService; let testConfigurationService: TestConfigurationService; let testObject: ExperimentService; + let activationEvent: Emitter; let installEvent: Emitter, didInstallEvent: Emitter, uninstallEvent: Emitter, @@ -72,7 +77,10 @@ suite('Experiment Service', () => { didInstallEvent = new Emitter(); uninstallEvent = new Emitter(); didUninstallEvent = new Emitter(); + activationEvent = new Emitter(); + instantiationService.stub(IExtensionService, TestExtensionService); + instantiationService.stub(IExtensionService, 'onWillActivateByEvent', activationEvent.event); instantiationService.stub(IExtensionManagementService, ExtensionManagementService); instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event); @@ -161,6 +169,36 @@ suite('Experiment Service', () => { }); }); + test('filters out experiments with newer schema versions', async () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + // no version == 0 + }, + { + id: 'experiment2', + schemaVersion: currentSchemaVersion, + }, + { + id: 'experiment3', + schemaVersion: currentSchemaVersion + 1, + }, + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + const actual = await Promise.all([ + testObject.getExperimentById('experiment1'), + testObject.getExperimentById('experiment2'), + testObject.getExperimentById('experiment3'), + ]); + + assert.equal(actual[0]?.id, 'experiment1'); + assert.equal(actual[1]?.id, 'experiment2'); + assert.equal(actual[2], undefined); + }); + test('Insiders only experiment shouldnt be enabled in stable', () => { experimentData = { experiments: [ @@ -270,6 +308,254 @@ suite('Experiment Service', () => { }); }); + test('Activation event experiment with not enough events should be evaluating', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + activationEvent: { + event: 'my:event', + minEvents: 5, + } + } + } + ] + }; + + instantiationService.stub(IStorageService, 'get', (a: string, b: StorageScope, c?: string) => { + return a === 'experimentEventRecord-my-event' + ? JSON.stringify({ count: [2], mostRecentBucket: Date.now() }) + : undefined; + }); + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.Evaluating); + }); + }); + + test('Activation event works with enough events', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + activationEvent: { + event: 'my:event', + minEvents: 5, + } + } + } + ] + }; + + instantiationService.stub(IStorageService, 'get', (a: string, b: StorageScope, c?: string) => { + return a === 'experimentEventRecord-my-event' + ? JSON.stringify({ count: [10], mostRecentBucket: Date.now() }) + : undefined; + }); + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.Run); + }); + }); + + test('Activation event does not work with old data', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + activationEvent: { + event: 'my:event', + minEvents: 5, + } + } + } + ] + }; + + instantiationService.stub(IStorageService, 'get', (a: string, b: StorageScope, c?: string) => { + return a === 'experimentEventRecord-my-event' + ? JSON.stringify({ count: [10], mostRecentBucket: Date.now() - (1000 * 60 * 60 * 24 * 10) }) + : undefined; + }); + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.Evaluating); + }); + }); + + test('Parses activation records correctly', () => { + const timers = sinon.useFakeTimers(); // so Date.now() is stable + const oneDay = 1000 * 60 * 60 * 24; + teardown(() => timers.restore()); + + let rec = getCurrentActivationRecord(); + + // good default: + assert.deepEqual(rec, { + count: [0, 0, 0, 0, 0, 0, 0], + mostRecentBucket: Date.now(), + }); + + rec.count[0] = 1; + timers.tick(1); + rec = getCurrentActivationRecord(rec); + + // does not advance unnecessarily + assert.deepEqual(getCurrentActivationRecord(rec), { + count: [1, 0, 0, 0, 0, 0, 0], + mostRecentBucket: Date.now() - 1, + }); + + // advances time + timers.tick(oneDay * 3); + rec = getCurrentActivationRecord(rec); + assert.deepEqual(getCurrentActivationRecord(rec), { + count: [0, 0, 0, 1, 0, 0, 0], + mostRecentBucket: Date.now() - 1, + }); + + // rotates off time + timers.tick(oneDay * 4); + rec.count[0] = 2; + rec = getCurrentActivationRecord(rec); + assert.deepEqual(getCurrentActivationRecord(rec), { + count: [0, 0, 0, 0, 2, 0, 0], + mostRecentBucket: Date.now() - 1, + }); + }); + + test('Activation event updates', async () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + activationEvent: { + event: 'my:event', + minEvents: 2, + } + } + } + ] + }; + + instantiationService.stub(IStorageService, 'get', (a: string, b: StorageScope, c?: string) => { + return a === 'experimentEventRecord-my-event' + ? JSON.stringify({ count: [10, 0, 0, 0, 0, 0, 0], mostRecentBucket: Date.now() - (1000 * 60 * 60 * 24 * 2) }) + : undefined; + }); + + let didGetCall = false; + instantiationService.stub(IStorageService, 'store', (key: string, value: string, scope: StorageScope) => { + if (key.includes('experimentEventRecord')) { + didGetCall = true; + assert.equal(key, 'experimentEventRecord-my-event'); + assert.deepEqual(JSON.parse(value).count, [1, 0, 10, 0, 0, 0, 0]); + assert.equal(scope, StorageScope.GLOBAL); + } + }); + + testObject = instantiationService.createInstance(TestExperimentService); + 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: [ + { + id: 'experiment1', + enabled: true, + condition: { + userSetting: { neat: true } + } + } + ] + }; + + instantiationService.stub(IConfigurationService, 'getValue', + (key: string) => key === 'neat' ? false : undefined); + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.NoRun); + }); + }); + + test('Experiment matching user setting should be enabled', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + userSetting: { neat: true } + } + } + ] + }; + + instantiationService.stub(IConfigurationService, 'getValue', + (key: string) => key === 'neat' ? true : undefined); + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.enabled, true); + assert.equal(result.state, ExperimentState.Run); + }); + }); + test('Experiment with no matching display language should be disabled', () => { experimentData = { experiments: [ @@ -483,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); @@ -490,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/dynamicWorkspaceRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts new file mode 100644 index 00000000000..39694d6c0a7 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; +import { isNumber } from 'vs/base/common/types'; +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + +type DynamicWorkspaceRecommendationsClassification = { + count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + cache: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; + +type IStoredDynamicWorkspaceRecommendations = { recommendations: string[], timestamp: number }; +const dynamicWorkspaceRecommendationsStorageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations'; +const milliSecondsInADay = 1000 * 60 * 60 * 24; + +export class DynamicWorkspaceRecommendations extends ExtensionRecommendations { + + private _recommendations: ExtensionRecommendation[] = []; + get recommendations(): ReadonlyArray { return this._recommendations; } + + constructor( + isExtensionAllowedToBeRecommended: (extensionId: string) => boolean, + @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService, + @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IFileService private readonly fileService: IFileService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @INotificationService notificationService: INotificationService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + ) { + super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService); + } + + protected async doActivate(): Promise { + await this.fetch(); + this._register(this.contextService.onDidChangeWorkbenchState(() => this._recommendations = [])); + } + + /** + * Fetch extensions used by others on the same workspace as recommendations + */ + private async fetch(): Promise { + this._register(this.contextService.onDidChangeWorkbenchState(() => this._recommendations = [])); + + if (this._recommendations.length + || this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER + || !this.fileService.canHandleResource(this.contextService.getWorkspace().folders[0].uri) + ) { + return; + } + + const folder = this.contextService.getWorkspace().folders[0]; + const cachedDynamicWorkspaceRecommendations = this.getCachedDynamicWorkspaceRecommendations(); + if (cachedDynamicWorkspaceRecommendations) { + this._recommendations = cachedDynamicWorkspaceRecommendations.map(id => this.toExtensionRecommendation(id, folder)); + this.telemetryService.publicLog2<{ count: number, cache: number }, DynamicWorkspaceRecommendationsClassification>('dynamicWorkspaceRecommendations', { count: this._recommendations.length, cache: 1 }); + return; + } + + const [hashedRemotes1, hashedRemotes2] = await Promise.all([this.workspaceTagsService.getHashedRemotesFromUri(folder.uri, false), this.workspaceTagsService.getHashedRemotesFromUri(folder.uri, true)]); + const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []); + if (!hashedRemotes.length) { + return; + } + + const workspacesTips = await this.extensionTipsService.getAllWorkspacesTips(); + if (!workspacesTips.length) { + return; + } + + for (const hashedRemote of hashedRemotes) { + const workspaceTip = workspacesTips.filter(workspaceTip => isNonEmptyArray(workspaceTip.remoteSet) && workspaceTip.remoteSet.indexOf(hashedRemote) > -1)[0]; + if (workspaceTip) { + this._recommendations = workspaceTip.recommendations.map(id => this.toExtensionRecommendation(id, folder)); + this.storageService.store(dynamicWorkspaceRecommendationsStorageKey, JSON.stringify({ recommendations: workspaceTip.recommendations, timestamp: Date.now() }), StorageScope.WORKSPACE); + this.telemetryService.publicLog2<{ count: number, cache: number }, DynamicWorkspaceRecommendationsClassification>('dynamicWorkspaceRecommendations', { count: this._recommendations.length, cache: 0 }); + return; + } + } + } + + private getCachedDynamicWorkspaceRecommendations(): string[] | undefined { + try { + const storedDynamicWorkspaceRecommendations: IStoredDynamicWorkspaceRecommendations = JSON.parse(this.storageService.get(dynamicWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, '{}')); + if (isNonEmptyArray(storedDynamicWorkspaceRecommendations.recommendations) + && isNumber(storedDynamicWorkspaceRecommendations.timestamp) + && storedDynamicWorkspaceRecommendations.timestamp > 0 + && (Date.now() - storedDynamicWorkspaceRecommendations.timestamp) / milliSecondsInADay < 14) { + return storedDynamicWorkspaceRecommendations.recommendations; + } + } catch (e) { + this.storageService.remove(dynamicWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE); + } + return undefined; + } + + private toExtensionRecommendation(extensionId: string, folder: IWorkspaceFolder): ExtensionRecommendation { + return { + extensionId: extensionId.toLowerCase(), + source: 'dynamic', + reason: { + reasonId: ExtensionRecommendationReason.DynamicWorkspace, + reasonText: localize('dynamicWorkspaceRecommendation', "This extension may interest you because it's popular among users of the {0} repository.", folder.name) + } + }; + } +} + diff --git a/src/vs/workbench/contrib/extensions/browser/exeBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/exeBasedRecommendations.ts new file mode 100644 index 00000000000..e168e3f2002 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/exeBasedRecommendations.ts @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionTipsService, IExecutableBasedExtensionTip, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { timeout } from 'vs/base/common/async'; +import { localize } from 'vs/nls'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { basename } from 'vs/base/common/path'; +import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ShowRecommendationsOnlyOnDemandKey } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + +type ExeExtensionRecommendationsClassification = { + extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + exeName: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; +}; + +export class ExeBasedRecommendations extends ExtensionRecommendations { + + readonly _recommendations: ExtensionRecommendation[] = []; + get recommendations(): ReadonlyArray { return this._recommendations; } + + constructor( + isExtensionAllowedToBeRecommended: (extensionId: string) => boolean, + @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @ILifecycleService lifecycleService: ILifecycleService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @INotificationService notificationService: INotificationService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + ) { + super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService); + + /* + 3s has come out to be the good number to fetch and prompt important exe based recommendations + Also fetch important exe based recommendations for reporting telemetry + */ + timeout(3000).then(() => this.fetchAndPromptImportantExeBasedRecommendations()); + + if (!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)) { + lifecycleService.when(LifecyclePhase.Eventually).then(() => this.activate()); + } + } + + protected async doActivate(): Promise { + const otherExectuableBasedTips = await this.extensionTipsService.getOtherExecutableBasedTips(); + otherExectuableBasedTips.forEach(tip => this._recommendations.push(this.toExtensionRecommendation(tip))); + } + + private async fetchAndPromptImportantExeBasedRecommendations(): Promise { + const importantExeBasedRecommendations: IStringDictionary = {}; + const importantExectuableBasedTips = await this.extensionTipsService.getImportantExecutableBasedTips(); + importantExectuableBasedTips.forEach(tip => { + this._recommendations.push(this.toExtensionRecommendation(tip)); + importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip; + }); + + const local = await this.extensionManagementService.getInstalled(ExtensionType.User); + const { installed, uninstalled } = this.groupByInstalled(Object.keys(importantExeBasedRecommendations), local); + + /* Log installed and uninstalled exe based recommendations */ + for (const extensionId of installed) { + const tip = importantExeBasedRecommendations[extensionId]; + this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) }); + } + for (const extensionId of uninstalled) { + const tip = importantExeBasedRecommendations[extensionId]; + this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:notInstalled', { extensionId, exeName: basename(tip.windowsPath!) }); + } + + this.promptImportantExeBasedRecommendations(uninstalled, importantExeBasedRecommendations); + } + + private promptImportantExeBasedRecommendations(recommendations: string[], importantExeBasedRecommendations: IStringDictionary): void { + if (this.hasToIgnoreRecommendationNotifications()) { + return; + } + recommendations = this.filterIgnoredOrNotAllowed(recommendations); + if (recommendations.length === 0) { + return; + } + + const extensionId = recommendations[0]; + const tip = importantExeBasedRecommendations[extensionId]; + const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!)); + this.promptImportantExtensionInstallNotification(extensionId, message); + } + + private groupByInstalled(recommendationsToSuggest: string[], local: ILocalExtension[]): { installed: string[], uninstalled: string[] } { + const installed: string[] = [], uninstalled: string[] = []; + const installedExtensionsIds = local.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); + recommendationsToSuggest.forEach(id => { + if (installedExtensionsIds.has(id.toLowerCase())) { + installed.push(id); + } else { + uninstalled.push(id); + } + }); + return { installed, uninstalled }; + } + + private toExtensionRecommendation(tip: IExecutableBasedExtensionTip): ExtensionRecommendation { + return { + extensionId: tip.extensionId.toLowerCase(), + source: 'executable', + reason: { + reasonId: ExtensionRecommendationReason.Executable, + reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", tip.friendlyName) + } + }; + } + +} + diff --git a/src/vs/workbench/contrib/extensions/browser/experimentalRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/experimentalRecommendations.ts new file mode 100644 index 00000000000..1e30aee3b93 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/experimentalRecommendations.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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + +export class ExperimentalRecommendations extends ExtensionRecommendations { + + private _recommendations: ExtensionRecommendation[] = []; + get recommendations(): ReadonlyArray { return this._recommendations; } + + constructor( + isExtensionAllowedToBeRecommended: (extensionId: string) => boolean, + @IExperimentService private readonly experimentService: IExperimentService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @INotificationService notificationService: INotificationService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + ) { + super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService); + } + + /** + * Fetch extensions used by others on the same workspace as recommendations + */ + protected async doActivate(): Promise { + const experiments = await this.experimentService.getExperimentsByType(ExperimentActionType.AddToRecommendations); + for (const { action, state } of experiments) { + if (state === ExperimentState.Run && isNonEmptyArray(action?.properties?.recommendations) && action?.properties?.recommendationReason) { + action.properties.recommendations.forEach((extensionId: string) => this._recommendations.push({ + extensionId: extensionId.toLowerCase(), + source: 'experimental', + reason: { + reasonId: ExtensionRecommendationReason.Experimental, + reasonText: action.properties.recommendationReason + } + })); + } + } + } + +} + diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index be1a3fa8093..e266328d202 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -19,7 +19,7 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionManifest, IKeyBinding, IView, IViewContainer, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; @@ -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 { @@ -148,6 +149,7 @@ interface IExtensionEditorTemplate { preview: HTMLElement; builtin: HTMLElement; license: HTMLElement; + version: HTMLElement; publisher: HTMLElement; installCount: HTMLElement; rating: HTMLElement; @@ -187,7 +189,7 @@ export class ExtensionEditor extends BaseEditor { @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, @IOpenerService private readonly openerService: IOpenerService, - @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService, + @IExtensionRecommendationsService private readonly extensionRecommendationsService: IExtensionRecommendationsService, @IStorageService storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @@ -204,6 +206,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')); @@ -237,6 +240,9 @@ export class ExtensionEditor extends BaseEditor { license.style.display = 'none'; license.tabIndex = 0; + const version = append(subtitle, $('span.version')); + version.textContent = localize('version', 'Version'); + const description = append(details, $('.description')); const extensionActions = append(details, $('.actions')); @@ -278,6 +284,7 @@ export class ExtensionEditor extends BaseEditor { icon, iconContainer, identifier, + version, ignoreActionbar, installCount, license, @@ -308,15 +315,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; @@ -339,9 +344,10 @@ export class ExtensionEditor extends BaseEditor { template.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none'; template.publisher.textContent = extension.publisherDisplayName; + template.version.textContent = extension.version; template.description.textContent = extension.description; - const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); + const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); let recommendationsData = {}; if (extRecommendations[extension.identifier.id.toLowerCase()]) { recommendationsData = { recommendationReason: extRecommendations[extension.identifier.id.toLowerCase()].reasonId }; @@ -397,10 +403,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 +432,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 { @@ -463,12 +472,12 @@ export class ExtensionEditor extends BaseEditor { this.transientDisposables.add(ignoreAction); this.transientDisposables.add(undoIgnoreAction); - const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); + const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { ignoreAction.enabled = true; template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; show(template.subtextContainer); - } else if (this.extensionTipsService.getAllIgnoredRecommendations().global.indexOf(extension.identifier.id.toLowerCase()) !== -1) { + } else if (this.extensionRecommendationsService.getIgnoredRecommendations().indexOf(extension.identifier.id.toLowerCase()) !== -1) { undoIgnoreAction.enabled = true; template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); show(template.subtextContainer); @@ -477,11 +486,11 @@ export class ExtensionEditor extends BaseEditor { template.subtext.textContent = ''; } - this.extensionTipsService.onRecommendationChange(change => { + this.extensionRecommendationsService.onRecommendationChange(change => { if (change.extensionId.toLowerCase() === extension.identifier.id.toLowerCase()) { if (change.isRecommended) { undoIgnoreAction.enabled = false; - const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); + const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { ignoreAction.enabled = true; template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; @@ -507,6 +516,7 @@ export class ExtensionEditor extends BaseEditor { if (e.enabled === false) { hide(template.subtextContainer); } + this.layout(); })); } @@ -568,7 +578,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 +586,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 +605,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 +625,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 +653,7 @@ export class ExtensionEditor extends BaseEditor { body { padding: 10px 20px; line-height: 22px; - max-width: 780px; + max-width: 882px; margin: 0 auto; } @@ -823,10 +832,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 +952,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 { @@ -1060,7 +1095,7 @@ export class ExtensionEditor extends BaseEditor { } private renderCustomEditors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { - const webviewEditors = manifest.contributes?.webviewEditors || []; + const webviewEditors = manifest.contributes?.customEditors || []; if (!webviewEditors.length) { return false; } @@ -1137,7 +1172,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 +1237,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 +1497,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/extensionRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendations.ts new file mode 100644 index 00000000000..eba729c554b --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendations.ts @@ -0,0 +1,133 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { localize } from 'vs/nls'; +import { InstallRecommendedExtensionAction, ShowRecommendedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { ExtensionRecommendationSource, IExtensionRecommendationReson } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionsConfiguration, ConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + +type ExtensionRecommendationsNotificationClassification = { + userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; +}; + +const ignoreImportantExtensionRecommendation = 'extensionsAssistant/importantRecommendationsIgnore'; +const choiceNever = localize('neverShowAgain', "Don't Show Again"); + +export type ExtensionRecommendation = { + readonly extensionId: string, + readonly source: ExtensionRecommendationSource; + readonly reason: IExtensionRecommendationReson; +}; + +export abstract class ExtensionRecommendations extends Disposable { + + readonly abstract recommendations: ReadonlyArray; + protected abstract doActivate(): Promise; + + constructor( + protected readonly isExtensionAllowedToBeRecommended: (extensionId: string) => boolean, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @INotificationService protected readonly notificationService: INotificationService, + @ITelemetryService protected readonly telemetryService: ITelemetryService, + @IStorageService protected readonly storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + ) { + super(); + storageKeysSyncRegistryService.registerStorageKey({ key: ignoreImportantExtensionRecommendation, version: 1 }); + } + + private _activationPromise: Promise | null = null; + get activated(): boolean { return this._activationPromise !== null; } + activate(): Promise { + if (!this._activationPromise) { + this._activationPromise = this.doActivate(); + } + return this._activationPromise; + } + + protected promptImportantExtensionInstallNotification(extensionId: string, message: string): void { + this.notificationService.prompt(Severity.Info, message, + [{ + label: localize('install', 'Install'), + run: () => { + this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId }); + this.instantiationService.createInstance(InstallRecommendedExtensionAction, extensionId).run(); + } + }, { + label: localize('showRecommendations', "Show Recommendations"), + run: () => { + this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId }); + + const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); + recommendationsAction.run(); + recommendationsAction.dispose(); + } + }, { + label: choiceNever, + isSecondary: true, + run: () => { + this.addToImportantRecommendationsIgnore(extensionId); + this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId }); + this.notificationService.prompt( + Severity.Info, + localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), + [{ + label: localize('ignoreAll', "Yes, Ignore All"), + run: () => this.setIgnoreRecommendationsConfig(true) + }, { + label: localize('no', "No"), + run: () => this.setIgnoreRecommendationsConfig(false) + }] + ); + } + }], + { + sticky: true, + onCancel: () => { + this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId }); + } + } + ); + } + + protected hasToIgnoreRecommendationNotifications(): boolean { + const config = this.configurationService.getValue(ConfigurationKey); + return config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand; + } + + protected filterIgnoredOrNotAllowed(recommendationsToSuggest: string[]): string[] { + const importantRecommendationsIgnoreList = (JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendation, StorageScope.GLOBAL, '[]'))).map(e => e.toLowerCase()); + return recommendationsToSuggest.filter(id => { + if (importantRecommendationsIgnoreList.indexOf(id) !== -1) { + return false; + } + if (!this.isExtensionAllowedToBeRecommended(id)) { + return false; + } + return true; + }); + } + + private addToImportantRecommendationsIgnore(id: string) { + const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendation, StorageScope.GLOBAL, '[]')); + importantRecommendationsIgnoreList.push(id.toLowerCase()); + this.storageService.store(ignoreImportantExtensionRecommendation, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL); + } + + private setIgnoreRecommendationsConfig(configVal: boolean) { + this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); + } + +} + diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts new file mode 100644 index 00000000000..87f7baa074d --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -0,0 +1,268 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IExtensionManagementService, IExtensionGalleryService, InstallOperation, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionRecommendationsService, ExtensionRecommendationReason, RecommendationChangeNotification, IExtensionRecommendation, ExtensionRecommendationSource } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ShowRecommendationsOnlyOnDemandKey } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { distinct, shuffle } from 'vs/base/common/arrays'; +import { Emitter, Event } from 'vs/base/common/event'; +import { assign } from 'vs/base/common/objects'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { DynamicWorkspaceRecommendations } from 'vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations'; +import { ExeBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/exeBasedRecommendations'; +import { ExperimentalRecommendations } from 'vs/workbench/contrib/extensions/browser/experimentalRecommendations'; +import { WorkspaceRecommendations } from 'vs/workbench/contrib/extensions/browser/workspaceRecommendations'; +import { FileBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/fileBasedRecommendations'; +import { KeymapRecommendations } from 'vs/workbench/contrib/extensions/browser/keymapRecommendations'; +import { ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + +type IgnoreRecommendationClassification = { + recommendationReason: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; +}; + +const ignoredRecommendationsStorageKey = 'extensionsAssistant/ignored_recommendations'; + +export class ExtensionRecommendationsService extends Disposable implements IExtensionRecommendationsService { + + _serviceBrand: undefined; + + // Recommendations + private readonly fileBasedRecommendations: FileBasedRecommendations; + private readonly workspaceRecommendations: WorkspaceRecommendations; + private readonly experimentalRecommendations: ExperimentalRecommendations; + private readonly exeBasedRecommendations: ExeBasedRecommendations; + private readonly dynamicWorkspaceRecommendations: DynamicWorkspaceRecommendations; + private readonly keymapRecommendations: KeymapRecommendations; + + // Ignored Recommendations + private globallyIgnoredRecommendations: string[] = []; + + public loadWorkspaceConfigPromise: Promise; + private sessionSeed: number; + + private readonly _onRecommendationChange = this._register(new Emitter()); + onRecommendationChange: Event = this._onRecommendationChange.event; + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @ILifecycleService lifecycleService: ILifecycleService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IStorageService private readonly storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + ) { + super(); + + storageKeysSyncRegistryService.registerStorageKey({ key: ignoredRecommendationsStorageKey, version: 1 }); + + const isExtensionAllowedToBeRecommended = (extensionId: string) => this.isExtensionAllowedToBeRecommended(extensionId); + this.workspaceRecommendations = instantiationService.createInstance(WorkspaceRecommendations, isExtensionAllowedToBeRecommended); + this.fileBasedRecommendations = instantiationService.createInstance(FileBasedRecommendations, isExtensionAllowedToBeRecommended); + this.experimentalRecommendations = instantiationService.createInstance(ExperimentalRecommendations, isExtensionAllowedToBeRecommended); + this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations, isExtensionAllowedToBeRecommended); + this.dynamicWorkspaceRecommendations = instantiationService.createInstance(DynamicWorkspaceRecommendations, isExtensionAllowedToBeRecommended); + this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations, isExtensionAllowedToBeRecommended); + + if (!this.isEnabled()) { + this.sessionSeed = 0; + this.loadWorkspaceConfigPromise = Promise.resolve(); + return; + } + + this.sessionSeed = +new Date(); + this.globallyIgnoredRecommendations = this.getCachedIgnoredRecommendations(); + + // Activation + this.loadWorkspaceConfigPromise = this.workspaceRecommendations.activate().then(() => this.fileBasedRecommendations.activate()); + this.experimentalRecommendations.activate(); + this.keymapRecommendations.activate(); + if (!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)) { + lifecycleService.when(LifecyclePhase.Eventually).then(() => this.activateProactiveRecommendations()); + } + + this._register(this.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e))); + this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + } + + private isEnabled(): boolean { + return this.galleryService.isEnabled() && !this.environmentService.extensionDevelopmentLocationURI; + } + + private async activateProactiveRecommendations(): Promise { + await Promise.all([this.dynamicWorkspaceRecommendations.activate(), this.exeBasedRecommendations.activate()]); + } + + getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } { + /* Activate proactive recommendations */ + this.activateProactiveRecommendations(); + + const output: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } = Object.create(null); + + const allRecommendations = [ + ...this.dynamicWorkspaceRecommendations.recommendations, + ...this.exeBasedRecommendations.recommendations, + ...this.experimentalRecommendations.recommendations, + ...this.fileBasedRecommendations.recommendations, + ...this.workspaceRecommendations.recommendations, + ...this.keymapRecommendations.recommendations, + ]; + + for (const { extensionId, reason } of allRecommendations) { + if (this.isExtensionAllowedToBeRecommended(extensionId)) { + output[extensionId.toLowerCase()] = reason; + } + } + + return output; + } + + async getOtherRecommendations(): Promise { + await this.activateProactiveRecommendations(); + + const recommendations = [ + ...this.exeBasedRecommendations.recommendations, + ...this.dynamicWorkspaceRecommendations.recommendations, + ...this.experimentalRecommendations.recommendations + ]; + + const extensionIds = distinct(recommendations.map(e => e.extensionId)) + .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)); + + shuffle(extensionIds, this.sessionSeed); + + return extensionIds.map(extensionId => { + const sources: ExtensionRecommendationSource[] = distinct(recommendations.filter(r => r.extensionId === extensionId).map(r => r.source)); + return ({ extensionId, sources }); + }); + } + + getKeymapRecommendations(): IExtensionRecommendation[] { + return this.toExtensionRecommendations(this.keymapRecommendations.recommendations); + } + + async getWorkspaceRecommendations(): Promise { + if (!this.isEnabled()) { + return []; + } + await this.workspaceRecommendations.activate(); + return this.toExtensionRecommendations(this.workspaceRecommendations.recommendations); + } + + getFileBasedRecommendations(): IExtensionRecommendation[] { + return this.toExtensionRecommendations(this.fileBasedRecommendations.recommendations); + } + + getIgnoredRecommendations(): ReadonlyArray { + return this.globallyIgnoredRecommendations; + } + + toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean) { + extensionId = extensionId.toLowerCase(); + const ignored = this.globallyIgnoredRecommendations.indexOf(extensionId) !== -1; + if (ignored === shouldIgnore) { + return; + } + + if (shouldIgnore) { + const reason = this.getAllRecommendationsWithReason()[extensionId]; + if (reason && reason.reasonId) { + this.telemetryService.publicLog2<{ extensionId: string, recommendationReason: ExtensionRecommendationReason }, IgnoreRecommendationClassification>('extensionsRecommendations:ignoreRecommendation', { extensionId, recommendationReason: reason.reasonId }); + } + } + + this.globallyIgnoredRecommendations = shouldIgnore ? [...this.globallyIgnoredRecommendations, extensionId] : this.globallyIgnoredRecommendations.filter(id => id !== extensionId); + this.storeCachedIgnoredRecommendations(this.globallyIgnoredRecommendations); + this._onRecommendationChange.fire({ extensionId, isRecommended: !shouldIgnore }); + } + + private onDidInstallExtension(e: DidInstallExtensionEvent): void { + if (e.gallery && e.operation === InstallOperation.Install) { + const extRecommendations = this.getAllRecommendationsWithReason() || {}; + const recommendationReason = extRecommendations[e.gallery.identifier.id.toLowerCase()]; + if (recommendationReason) { + /* __GDPR__ + "extensionGallery:install:recommendations" : { + "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } + */ + this.telemetryService.publicLog('extensionGallery:install:recommendations', assign(e.gallery.telemetryData, { recommendationReason: recommendationReason.reasonId })); + } + } + } + + private toExtensionRecommendations(recommendations: ReadonlyArray): IExtensionRecommendation[] { + const extensionIds = distinct(recommendations.map(e => e.extensionId)) + .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)); + + return extensionIds.map(extensionId => { + const sources: ExtensionRecommendationSource[] = distinct(recommendations.filter(r => r.extensionId === extensionId).map(r => r.source)); + return ({ extensionId, sources }); + }); + } + + private isExtensionAllowedToBeRecommended(id: string): boolean { + const allIgnoredRecommendations = [ + ...this.globallyIgnoredRecommendations, + ...this.workspaceRecommendations.ignoredRecommendations + ]; + return allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1; + } + + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + if (e.key === ignoredRecommendationsStorageKey && e.scope === StorageScope.GLOBAL + && this.ignoredRecommendationsValue !== this.getStoredIgnoredRecommendationsValue() /* This checks if current window changed the value or not */) { + this._ignoredRecommendationsValue = undefined; + this.globallyIgnoredRecommendations = this.getCachedIgnoredRecommendations(); + } + } + + private getCachedIgnoredRecommendations(): string[] { + const ignoredRecommendations: string[] = JSON.parse(this.ignoredRecommendationsValue); + return ignoredRecommendations.map(e => e.toLowerCase()); + } + + private storeCachedIgnoredRecommendations(ignoredRecommendations: string[]): void { + this.ignoredRecommendationsValue = JSON.stringify(ignoredRecommendations); + } + + private _ignoredRecommendationsValue: string | undefined; + private get ignoredRecommendationsValue(): string { + if (!this._ignoredRecommendationsValue) { + this._ignoredRecommendationsValue = this.getStoredIgnoredRecommendationsValue(); + } + + return this._ignoredRecommendationsValue; + } + + private set ignoredRecommendationsValue(ignoredRecommendationsValue: string) { + if (this.ignoredRecommendationsValue !== ignoredRecommendationsValue) { + this._ignoredRecommendationsValue = ignoredRecommendationsValue; + this.setStoredIgnoredRecommendationsValue(ignoredRecommendationsValue); + } + } + + private getStoredIgnoredRecommendationsValue(): string { + return this.storageService.get(ignoredRecommendationsStorageKey, StorageScope.GLOBAL, '[]'); + } + + private setStoredIgnoredRecommendationsValue(value: string): void { + this.storageService.store(ignoredRecommendationsStorageKey, value, StorageScope.GLOBAL); + } + +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts deleted file mode 100644 index 913da6725e9..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts +++ /dev/null @@ -1,1185 +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 { join, basename } from 'vs/base/common/path'; -import { forEach } from 'vs/base/common/collections'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { match } from 'vs/base/common/glob'; -import * as json from 'vs/base/common/json'; -import { IExtensionManagementService, IExtensionGalleryService, EXTENSION_IDENTIFIER_PATTERN, InstallOperation, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionTipsService, ExtensionRecommendationReason, IExtensionsConfigContent, RecommendationChangeNotification, IExtensionRecommendation, ExtensionRecommendationSource, IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITextModel } from 'vs/editor/common/model'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import Severity from 'vs/base/common/severity'; -import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewPaneContainer, IExtensionsWorkbenchService, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { flatten, distinct, shuffle, coalesce } from 'vs/base/common/arrays'; -import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IRequestService, asJson } from 'vs/platform/request/common/request'; -import { isNumber } from 'vs/base/common/types'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Emitter, Event } from 'vs/base/common/event'; -import { assign } from 'vs/base/common/objects'; -import { URI } from 'vs/base/common/uri'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { extname } from 'vs/base/common/resources'; -import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/productService'; -import { timeout } from 'vs/base/common/async'; -import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; -import { setImmediate, isWeb } from 'vs/base/common/platform'; -import { platform, env as processEnv } from 'vs/base/common/process'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { Schemas } from 'vs/base/common/network'; - -const milliSecondsInADay = 1000 * 60 * 60 * 24; -const choiceNever = localize('neverShowAgain', "Don't Show Again"); -const searchMarketplace = localize('searchMarketplace', "Search Marketplace"); -const processedFileExtensions: string[] = []; - -interface IDynamicWorkspaceRecommendations { - remoteSet: string[]; - recommendations: string[]; -} - -function caseInsensitiveGet(obj: { [key: string]: T }, key: string): T | undefined { - if (!obj) { - return undefined; - } - for (const _key in obj) { - if (Object.hasOwnProperty.call(obj, _key) && _key.toLowerCase() === key.toLowerCase()) { - return obj[_key]; - } - } - return undefined; -} - -export class ExtensionTipsService extends Disposable implements IExtensionTipsService { - - _serviceBrand: undefined; - - private _fileBasedRecommendations: { [id: string]: { recommendedTime: number, sources: ExtensionRecommendationSource[] }; } = Object.create(null); - private _exeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null); - private _importantExeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null); - private _availableRecommendations: { [pattern: string]: string[] } = Object.create(null); - private _allWorkspaceRecommendedExtensions: IExtensionRecommendation[] = []; - private _dynamicWorkspaceRecommendations: string[] = []; - private _experimentalRecommendations: { [id: string]: string } = Object.create(null); - private _allIgnoredRecommendations: string[] = []; - private _globallyIgnoredRecommendations: string[] = []; - private _workspaceIgnoredRecommendations: string[] = []; - private _extensionsRecommendationsUrl: string | undefined; - public loadWorkspaceConfigPromise: Promise; - private proactiveRecommendationsFetched: boolean = false; - - private readonly _onRecommendationChange = this._register(new Emitter()); - onRecommendationChange: Event = this._onRecommendationChange.event; - private sessionSeed: number; - - constructor( - @IExtensionGalleryService private readonly _galleryService: IExtensionGalleryService, - @IModelService private readonly _modelService: IModelService, - @IStorageService private readonly storageService: IStorageService, - @IExtensionManagementService private readonly extensionsService: IExtensionManagementService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IFileService private readonly fileService: IFileService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IExtensionService private readonly extensionService: IExtensionService, - @IRequestService private readonly requestService: IRequestService, - @IViewletService private readonly viewletService: IViewletService, - @INotificationService private readonly notificationService: INotificationService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, - @IExperimentService private readonly experimentService: IExperimentService, - @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, - @IProductService private readonly productService: IProductService - ) { - super(); - - if (!this.isEnabled()) { - this.sessionSeed = 0; - this.loadWorkspaceConfigPromise = Promise.resolve(); - return; - } - - if (this.productService.extensionsGallery && this.productService.extensionsGallery.recommendationsUrl) { - this._extensionsRecommendationsUrl = this.productService.extensionsGallery.recommendationsUrl; - } - - this.sessionSeed = +new Date(); - - let globallyIgnored = JSON.parse(this.storageService.get('extensionsAssistant/ignored_recommendations', StorageScope.GLOBAL, '[]')); - this._globallyIgnoredRecommendations = globallyIgnored.map(id => id.toLowerCase()); - - this.fetchCachedDynamicWorkspaceRecommendations(); - this.fetchFileBasedRecommendations(); - this.fetchExperimentalRecommendations(); - if (!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)) { - this.fetchProactiveRecommendations(true); - } - - this.loadWorkspaceConfigPromise = this.getWorkspaceRecommendations().then(() => { - this.promptWorkspaceRecommendations(); - this._register(this._modelService.onModelAdded(this.promptFiletypeBasedRecommendations, this)); - this._modelService.getModels().forEach(model => this.promptFiletypeBasedRecommendations(model)); - }); - - this._register(this.contextService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e))); - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (!this.proactiveRecommendationsFetched && !this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)) { - this.fetchProactiveRecommendations(); - } - })); - this._register(this.extensionManagementService.onDidInstallExtension(e => { - if (e.gallery && e.operation === InstallOperation.Install) { - const extRecommendations = this.getAllRecommendationsWithReason() || {}; - const recommendationReason = extRecommendations[e.gallery.identifier.id.toLowerCase()]; - if (recommendationReason) { - /* __GDPR__ - "extensionGallery:install:recommendations" : { - "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "${include}": [ - "${GalleryExtensionTelemetryData}" - ] - } - */ - this.telemetryService.publicLog('extensionGallery:install:recommendations', assign(e.gallery.telemetryData, { recommendationReason: recommendationReason.reasonId })); - } - } - })); - } - - private isEnabled(): boolean { - return this._galleryService.isEnabled() && !this.environmentService.extensionDevelopmentLocationURI; - } - - getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } { - let output: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } = Object.create(null); - - if (!this.proactiveRecommendationsFetched) { - return output; - } - - forEach(this._experimentalRecommendations, entry => output[entry.key.toLowerCase()] = { - reasonId: ExtensionRecommendationReason.Experimental, - reasonText: entry.value - }); - - if (this.contextService.getWorkspace().folders && this.contextService.getWorkspace().folders.length === 1) { - const currentRepo = this.contextService.getWorkspace().folders[0].name; - - this._dynamicWorkspaceRecommendations.forEach(id => output[id.toLowerCase()] = { - reasonId: ExtensionRecommendationReason.DynamicWorkspace, - reasonText: localize('dynamicWorkspaceRecommendation', "This extension may interest you because it's popular among users of the {0} repository.", currentRepo) - }); - } - - forEach(this._exeBasedRecommendations, entry => output[entry.key.toLowerCase()] = { - reasonId: ExtensionRecommendationReason.Executable, - reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", entry.value.friendlyName) - }); - - forEach(this._fileBasedRecommendations, entry => output[entry.key.toLowerCase()] = { - reasonId: ExtensionRecommendationReason.File, - reasonText: localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened.") - }); - - this._allWorkspaceRecommendedExtensions.forEach(({ extensionId }) => output[extensionId.toLowerCase()] = { - reasonId: ExtensionRecommendationReason.Workspace, - reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.") - }); - - for (const id of this._allIgnoredRecommendations) { - delete output[id]; - } - - return output; - } - - getAllIgnoredRecommendations(): { global: string[], workspace: string[] } { - return { - global: this._globallyIgnoredRecommendations, - workspace: this._workspaceIgnoredRecommendations - }; - } - - toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean) { - const lowerId = extensionId.toLowerCase(); - if (shouldIgnore) { - const reason = this.getAllRecommendationsWithReason()[lowerId]; - if (reason && reason.reasonId) { - /* __GDPR__ - "extensionsRecommendations:ignoreRecommendation" : { - "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionsRecommendations:ignoreRecommendation', { id: extensionId, recommendationReason: reason.reasonId }); - } - } - - this._globallyIgnoredRecommendations = shouldIgnore ? - distinct([...this._globallyIgnoredRecommendations, lowerId].map(id => id.toLowerCase())) : - this._globallyIgnoredRecommendations.filter(id => id !== lowerId); - - this.storageService.store('extensionsAssistant/ignored_recommendations', JSON.stringify(this._globallyIgnoredRecommendations), StorageScope.GLOBAL); - this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); - - this._onRecommendationChange.fire({ extensionId: extensionId, isRecommended: !shouldIgnore }); - } - - getKeymapRecommendations(): IExtensionRecommendation[] { - return (this.productService.keymapExtensionTips || []) - .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)) - .map(extensionId => ({ extensionId, sources: ['application'] })); - } - - //#region workspaceRecommendations - - getWorkspaceRecommendations(): Promise { - if (!this.isEnabled()) { return Promise.resolve([]); } - return this.fetchWorkspaceRecommendations() - .then(() => this._allWorkspaceRecommendedExtensions.filter(rec => this.isExtensionAllowedToBeRecommended(rec.extensionId))); - } - - /** - * Parse all extensions.json files, fetch workspace recommendations, filter out invalid and unwanted ones - */ - private fetchWorkspaceRecommendations(): Promise { - - if (!this.isEnabled) { return Promise.resolve(undefined); } - - return this.fetchExtensionRecommendationContents() - .then(result => this.validateExtensions(result.map(({ contents }) => contents)) - .then(({ invalidExtensions, message }) => { - - if (invalidExtensions.length > 0 && this.notificationService) { - this.notificationService.warn(`The below ${invalidExtensions.length} extension(s) in workspace recommendations have issues:\n${message}`); - } - - const seenUnWantedRecommendations: { [id: string]: boolean } = {}; - - this._allWorkspaceRecommendedExtensions = []; - this._workspaceIgnoredRecommendations = []; - - for (const contentsBySource of result) { - if (contentsBySource.contents.unwantedRecommendations) { - for (const r of contentsBySource.contents.unwantedRecommendations) { - const unwantedRecommendation = r.toLowerCase(); - if (!seenUnWantedRecommendations[unwantedRecommendation] && invalidExtensions.indexOf(unwantedRecommendation) === -1) { - this._workspaceIgnoredRecommendations.push(unwantedRecommendation); - seenUnWantedRecommendations[unwantedRecommendation] = true; - } - } - } - - if (contentsBySource.contents.recommendations) { - for (const r of contentsBySource.contents.recommendations) { - const extensionId = r.toLowerCase(); - if (invalidExtensions.indexOf(extensionId) === -1) { - let recommendation = this._allWorkspaceRecommendedExtensions.filter(r => r.extensionId === extensionId)[0]; - if (!recommendation) { - recommendation = { extensionId, sources: [] }; - this._allWorkspaceRecommendedExtensions.push(recommendation); - } - if (recommendation.sources.indexOf(contentsBySource.source) === -1) { - recommendation.sources.push(contentsBySource.source); - } - } - } - } - } - this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); - })); - } - - /** - * Parse all extensions.json files, fetch workspace recommendations - */ - private fetchExtensionRecommendationContents(): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource }[]> { - const workspace = this.contextService.getWorkspace(); - return Promise.all<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource } | null>([ - this.resolveWorkspaceExtensionConfig(workspace).then(contents => contents ? { contents, source: workspace } : null), - ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder).then(contents => contents ? { contents, source: workspaceFolder } : null)) - ]).then(contents => coalesce(contents)); - } - - /** - * Parse the extensions.json file for given workspace and return the recommendations - */ - private resolveWorkspaceExtensionConfig(workspace: IWorkspace): Promise { - if (!workspace.configuration) { - return Promise.resolve(null); - } - - return Promise.resolve(this.fileService.readFile(workspace.configuration) - .then(content => (json.parse(content.value.toString())['extensions']), err => null)); - } - - /** - * Parse the extensions.json files for given workspace folder and return the recommendations - */ - private resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise { - const extensionsJsonUri = workspaceFolder.toResource(EXTENSIONS_CONFIG); - - return Promise.resolve(this.fileService.resolve(extensionsJsonUri) - .then(() => this.fileService.readFile(extensionsJsonUri)) - .then(content => json.parse(content.value.toString()), err => null)); - } - - /** - * Validate the extensions.json file contents using regex and querying the gallery - */ - private async validateExtensions(contents: IExtensionsConfigContent[]): Promise<{ invalidExtensions: string[], message: string }> { - const extensionsContent: IExtensionsConfigContent = { - recommendations: distinct(flatten(contents.map(content => content.recommendations || []))), - unwantedRecommendations: distinct(flatten(contents.map(content => content.unwantedRecommendations || []))) - }; - - const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); - - const invalidExtensions: string[] = []; - let message = ''; - - const regexFilter = (ids: string[]) => { - return ids.filter((element, position) => { - if (ids.indexOf(element) !== position) { - // This is a duplicate entry, it doesn't hurt anybody - // but it shouldn't be sent in the gallery query - return false; - } else if (!regEx.test(element)) { - invalidExtensions.push(element.toLowerCase()); - message += `${element} (bad format) Expected: .\n`; - return false; - } - return true; - }); - }; - - const filteredWanted = regexFilter(extensionsContent.recommendations || []).map(x => x.toLowerCase()); - - if (filteredWanted.length) { - try { - let validRecommendations = (await this._galleryService.query({ names: filteredWanted, pageSize: filteredWanted.length }, CancellationToken.None)).firstPage - .map(extension => extension.identifier.id.toLowerCase()); - - if (validRecommendations.length !== filteredWanted.length) { - filteredWanted.forEach(element => { - if (validRecommendations.indexOf(element.toLowerCase()) === -1) { - invalidExtensions.push(element.toLowerCase()); - message += `${element} (not found in marketplace)\n`; - } - }); - } - } catch (e) { - console.warn('Error querying extensions gallery', e); - } - } - return { invalidExtensions, message }; - } - - private onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): void { - if (event.added.length) { - const oldWorkspaceRecommended = this._allWorkspaceRecommendedExtensions; - this.getWorkspaceRecommendations() - .then(currentWorkspaceRecommended => { - // Suggest only if at least one of the newly added recommendations was not suggested before - if (currentWorkspaceRecommended.some(current => oldWorkspaceRecommended.every(old => current.extensionId !== old.extensionId))) { - this.promptWorkspaceRecommendations(); - } - }); - } - this._dynamicWorkspaceRecommendations = []; - } - - /** - * Prompt the user to install workspace recommendations if there are any not already installed - */ - private promptWorkspaceRecommendations(): void { - const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; - const config = this.configurationService.getValue(ConfigurationKey); - const filteredRecs = this._allWorkspaceRecommendedExtensions.filter(rec => this.isExtensionAllowedToBeRecommended(rec.extensionId)); - - if (filteredRecs.length === 0 - || config.ignoreRecommendations - || config.showRecommendationsOnlyOnDemand - || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { - return; - } - - this.extensionsService.getInstalled(ExtensionType.User).then(local => { - local = local.filter(l => this.extensionEnablementService.getEnablementState(l) !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind - const recommendations = filteredRecs.filter(({ extensionId }) => local.every(local => !areSameExtensions({ id: extensionId }, local.identifier))); - - if (!recommendations.length) { - return Promise.resolve(undefined); - } - - return new Promise(c => { - this.notificationService.prompt( - Severity.Info, - localize('workspaceRecommended', "This workspace has extension recommendations."), - [{ - label: localize('installAll', "Install All"), - run: () => { - /* __GDPR__ - "extensionWorkspaceRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }); - - const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All"), recommendations); - installAllAction.run(); - installAllAction.dispose(); - - c(undefined); - } - }, { - label: localize('showRecommendations', "Show Recommendations"), - run: () => { - /* __GDPR__ - "extensionWorkspaceRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'show' }); - - const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); - showAction.run(); - showAction.dispose(); - - c(undefined); - } - }, { - label: choiceNever, - isSecondary: true, - run: () => { - /* __GDPR__ - "extensionWorkspaceRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' }); - this.storageService.store(storageKey, true, StorageScope.WORKSPACE); - - c(undefined); - } - }], - { - sticky: true, - onCancel: () => { - /* __GDPR__ - "extensionWorkspaceRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' }); - - c(undefined); - } - } - ); - }); - }); - } - - //#endregion - - //#region important exe based extension - - private async promptForImportantExeBasedExtension(): Promise { - - let recommendationsToSuggest = Object.keys(this._importantExeBasedRecommendations); - - const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); - recommendationsToSuggest = this.filterInstalled(recommendationsToSuggest, installed, (extensionId) => { - const tip = this._importantExeBasedRecommendations[extensionId]; - - /* __GDPR__ - "exeExtensionRecommendations:alreadyInstalled" : { - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, - "exeName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) }); - - }); - - if (recommendationsToSuggest.length === 0) { - return false; - } - - const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; - const config = this.configurationService.getValue(ConfigurationKey); - - if (config.ignoreRecommendations - || config.showRecommendationsOnlyOnDemand - || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { - return false; - } - - recommendationsToSuggest = this.filterIgnoredOrNotAllowed(recommendationsToSuggest); - if (recommendationsToSuggest.length === 0) { - return false; - } - - const extensionId = recommendationsToSuggest[0]; - const tip = this._importantExeBasedRecommendations[extensionId]; - const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!)); - - this.notificationService.prompt(Severity.Info, message, - [{ - label: localize('install', 'Install'), - run: () => { - /* __GDPR__ - "exeExtensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'install', extensionId }); - this.instantiationService.createInstance(InstallRecommendedExtensionAction, extensionId).run(); - } - }, { - label: localize('showRecommendations', "Show Recommendations"), - run: () => { - /* __GDPR__ - "exeExtensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'show', extensionId }); - - const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); - recommendationsAction.run(); - recommendationsAction.dispose(); - } - }, { - label: choiceNever, - isSecondary: true, - run: () => { - this.addToImportantRecommendationsIgnore(extensionId); - /* __GDPR__ - "exeExtensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId }); - this.notificationService.prompt( - Severity.Info, - localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), - [{ - label: localize('ignoreAll', "Yes, Ignore All"), - run: () => this.setIgnoreRecommendationsConfig(true) - }, { - label: localize('no', "No"), - run: () => this.setIgnoreRecommendationsConfig(false) - }] - ); - } - }], - { - sticky: true, - onCancel: () => { - /* __GDPR__ - "exeExtensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'cancelled', extensionId }); - } - } - ); - - return true; - } - - //#region fileBasedRecommendations - - getFileBasedRecommendations(): IExtensionRecommendation[] { - return Object.keys(this._fileBasedRecommendations) - .sort((a, b) => { - if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) { - if (!this.productService.extensionImportantTips || caseInsensitiveGet(this.productService.extensionImportantTips, a)) { - return -1; - } - if (caseInsensitiveGet(this.productService.extensionImportantTips, b)) { - return 1; - } - } - return this._fileBasedRecommendations[a].recommendedTime > this._fileBasedRecommendations[b].recommendedTime ? -1 : 1; - }) - .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)) - .map(extensionId => ({ extensionId, sources: this._fileBasedRecommendations[extensionId].sources })); - } - - /** - * Parse all file based recommendations from this.productService.extensionTips - * Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore - */ - private fetchFileBasedRecommendations() { - const extensionTips = this.productService.extensionTips; - if (!extensionTips) { - return; - } - - // group ids by pattern, like {**/*.md} -> [ext.foo1, ext.bar2] - this._availableRecommendations = Object.create(null); - forEach(extensionTips, entry => { - let { key: id, value: pattern } = entry; - let ids = this._availableRecommendations[pattern]; - if (!ids) { - this._availableRecommendations[pattern] = [id.toLowerCase()]; - } else { - ids.push(id.toLowerCase()); - } - }); - - if (this.productService.extensionImportantTips) { - forEach(this.productService.extensionImportantTips, entry => { - let { key: id, value } = entry; - const { pattern } = value; - let ids = this._availableRecommendations[pattern]; - if (!ids) { - this._availableRecommendations[pattern] = [id.toLowerCase()]; - } else { - ids.push(id.toLowerCase()); - } - }); - } - - const allRecommendations: string[] = flatten((Object.keys(this._availableRecommendations).map(key => this._availableRecommendations[key]))); - - // retrieve ids of previous recommendations - const storedRecommendationsJson = JSON.parse(this.storageService.get('extensionsAssistant/recommendations', StorageScope.GLOBAL, '[]')); - - if (Array.isArray(storedRecommendationsJson)) { - for (let id of storedRecommendationsJson) { - if (allRecommendations.indexOf(id) > -1) { - this._fileBasedRecommendations[id.toLowerCase()] = { recommendedTime: Date.now(), sources: ['cached'] }; - } - } - } else { - const now = Date.now(); - forEach(storedRecommendationsJson, entry => { - if (typeof entry.value === 'number') { - const diff = (now - entry.value) / milliSecondsInADay; - if (diff <= 7 && allRecommendations.indexOf(entry.key) > -1) { - this._fileBasedRecommendations[entry.key.toLowerCase()] = { recommendedTime: entry.value, sources: ['cached'] }; - } - } - }); - } - } - - /** - * Prompt the user to either install the recommended extension for the file type in the current editor model - * or prompt to search the marketplace if it has extensions that can support the file type - */ - private promptFiletypeBasedRecommendations(model: ITextModel): void { - const uri = model.uri; - const supportedSchemes = [Schemas.untitled, Schemas.file, Schemas.vscodeRemote]; - if (!uri || supportedSchemes.indexOf(uri.scheme) === -1) { - return; - } - - let fileExtension = extname(uri); - if (fileExtension) { - if (processedFileExtensions.indexOf(fileExtension) > -1) { - return; - } - processedFileExtensions.push(fileExtension); - } - - // re-schedule this bit of the operation to be off the critical path - in case glob-match is slow - setImmediate(async () => { - - let recommendationsToSuggest: string[] = []; - const now = Date.now(); - forEach(this._availableRecommendations, entry => { - let { key: pattern, value: ids } = entry; - if (match(pattern, model.uri.toString())) { - for (let id of ids) { - if (this.productService.extensionImportantTips && caseInsensitiveGet(this.productService.extensionImportantTips, id)) { - recommendationsToSuggest.push(id); - } - const filedBasedRecommendation = this._fileBasedRecommendations[id.toLowerCase()] || { recommendedTime: now, sources: [] }; - filedBasedRecommendation.recommendedTime = now; - if (!filedBasedRecommendation.sources.some(s => s instanceof URI && s.toString() === model.uri.toString())) { - filedBasedRecommendation.sources.push(model.uri); - } - this._fileBasedRecommendations[id.toLowerCase()] = filedBasedRecommendation; - } - } - }); - - this.storageService.store( - 'extensionsAssistant/recommendations', - JSON.stringify(Object.keys(this._fileBasedRecommendations).reduce((result, key) => { result[key] = this._fileBasedRecommendations[key].recommendedTime; return result; }, {} as { [key: string]: any })), - StorageScope.GLOBAL - ); - - const config = this.configurationService.getValue(ConfigurationKey); - if (config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand) { - return; - } - - const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); - if (await this.promptRecommendedExtensionForFileType(recommendationsToSuggest, installed)) { - return; - } - - if (fileExtension) { - fileExtension = fileExtension.substr(1); // Strip the dot - } - if (!fileExtension) { - return; - } - - await this.extensionService.whenInstalledExtensionsRegistered(); - const mimeTypes = guessMimeTypes(uri); - if (mimeTypes.length !== 1 || mimeTypes[0] !== MIME_UNKNOWN) { - return; - } - - this.promptRecommendedExtensionForFileExtension(fileExtension, installed); - }); - } - - private async promptRecommendedExtensionForFileType(recommendationsToSuggest: string[], installed: ILocalExtension[]): Promise { - - recommendationsToSuggest = this.filterIgnoredOrNotAllowed(recommendationsToSuggest); - if (recommendationsToSuggest.length === 0) { - return false; - } - - recommendationsToSuggest = this.filterInstalled(recommendationsToSuggest, installed); - if (recommendationsToSuggest.length === 0) { - return false; - } - - const id = recommendationsToSuggest[0]; - const entry = this.productService.extensionImportantTips ? caseInsensitiveGet(this.productService.extensionImportantTips, id) : undefined; - if (!entry) { - return false; - } - const name = entry.name; - let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", name); - if (entry.isExtensionPack) { - message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", name); - } - - this.notificationService.prompt(Severity.Info, message, - [{ - label: localize('install', 'Install'), - run: () => { - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'install', extensionId: name }); - this.instantiationService.createInstance(InstallRecommendedExtensionAction, id).run(); - } - }, { - label: localize('showRecommendations', "Show Recommendations"), - run: () => { - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'show', extensionId: name }); - - const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); - recommendationsAction.run(); - recommendationsAction.dispose(); - } - }, { - label: choiceNever, - isSecondary: true, - run: () => { - this.addToImportantRecommendationsIgnore(id); - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name }); - this.notificationService.prompt( - Severity.Info, - localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), - [{ - label: localize('ignoreAll', "Yes, Ignore All"), - run: () => this.setIgnoreRecommendationsConfig(true) - }, { - label: localize('no', "No"), - run: () => this.setIgnoreRecommendationsConfig(false) - }] - ); - } - }], - { - sticky: true, - onCancel: () => { - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name }); - } - } - ); - - return true; - } - - private async promptRecommendedExtensionForFileExtension(fileExtension: string, installed: ILocalExtension[]): Promise { - const fileExtensionSuggestionIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]')); - if (fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1) { - return; - } - - const text = `ext:${fileExtension}`; - const pager = await this.extensionWorkbenchService.queryGallery({ text, pageSize: 100 }, CancellationToken.None); - if (pager.firstPage.length === 0) { - return; - } - - const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); - if (pager.firstPage.some(e => installedExtensionsIds.has(e.identifier.id.toLowerCase()))) { - return; - } - - this.notificationService.prompt( - Severity.Info, - localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension), - [{ - label: searchMarketplace, - run: () => { - /* __GDPR__ - "fileExtensionSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension: fileExtension }); - this.viewletService.openViewlet('workbench.view.extensions', true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(`ext:${fileExtension}`); - viewlet.focus(); - }); - } - }, { - label: localize('dontShowAgainExtension', "Don't Show Again for '.{0}' files", fileExtension), - run: () => { - fileExtensionSuggestionIgnoreList.push(fileExtension); - this.storageService.store( - 'extensionsAssistant/fileExtensionsSuggestionIgnore', - JSON.stringify(fileExtensionSuggestionIgnoreList), - StorageScope.GLOBAL - ); - /* __GDPR__ - "fileExtensionSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension: fileExtension }); - } - }], - { - sticky: true, - onCancel: () => { - /* __GDPR__ - "fileExtensionSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension: fileExtension }); - } - } - ); - } - - private filterIgnoredOrNotAllowed(recommendationsToSuggest: string[]): string[] { - const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); - return recommendationsToSuggest.filter(id => { - if (importantRecommendationsIgnoreList.indexOf(id) !== -1) { - return false; - } - if (!this.isExtensionAllowedToBeRecommended(id)) { - return false; - } - return true; - }); - } - - private filterInstalled(recommendationsToSuggest: string[], installed: ILocalExtension[], onAlreadyInstalled?: (id: string) => void): string[] { - const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); - return recommendationsToSuggest.filter(id => { - if (installedExtensionsIds.has(id.toLowerCase())) { - if (onAlreadyInstalled) { - onAlreadyInstalled(id); - } - return false; - } - return true; - }); - } - - private addToImportantRecommendationsIgnore(id: string) { - const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); - importantRecommendationsIgnoreList.push(id); - this.storageService.store( - 'extensionsAssistant/importantRecommendationsIgnore', - JSON.stringify(importantRecommendationsIgnoreList), - StorageScope.GLOBAL - ); - } - - private setIgnoreRecommendationsConfig(configVal: boolean) { - this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); - if (configVal) { - const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; - this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE); - } - } - - //#endregion - - //#region otherRecommendations - - getOtherRecommendations(): Promise { - return this.fetchProactiveRecommendations().then(() => { - const others = distinct([ - ...Object.keys(this._exeBasedRecommendations), - ...this._dynamicWorkspaceRecommendations, - ...Object.keys(this._experimentalRecommendations), - ]).filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)); - shuffle(others, this.sessionSeed); - return others.map(extensionId => { - const sources: ExtensionRecommendationSource[] = []; - if (this._exeBasedRecommendations[extensionId]) { - sources.push('executable'); - } - if (this._dynamicWorkspaceRecommendations.indexOf(extensionId) !== -1) { - sources.push('dynamic'); - } - return ({ extensionId, sources }); - }); - }); - } - - private fetchProactiveRecommendations(calledDuringStartup?: boolean): Promise { - let fetchPromise = Promise.resolve(undefined); - if (!this.proactiveRecommendationsFetched) { - this.proactiveRecommendationsFetched = true; - - // Executable based recommendations carry out a lot of file stats, delay the resolution so that the startup is not affected - // 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])); - - } - return fetchPromise; - } - - /** - * If user has any of the tools listed in this.productService.exeBasedExtensionTips, fetch corresponding recommendations - */ - private async fetchExecutableRecommendations(important: boolean): Promise { - if (isWeb || !this.productService.exeBasedExtensionTips) { - return; - } - - const foundExecutables: Set = new Set(); - const findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => { - return this.fileService.exists(URI.file(path)).then(exists => { - if (exists && !foundExecutables.has(exeName)) { - foundExecutables.add(exeName); - (tip['recommendations'] || []).forEach(extensionId => { - if (tip.friendlyName) { - if (important) { - this._importantExeBasedRecommendations[extensionId.toLowerCase()] = tip; - } - this._exeBasedRecommendations[extensionId.toLowerCase()] = tip; - } - }); - } - }); - }; - - const promises: Promise[] = []; - // Loop through recommended extensions - forEach(this.productService.exeBasedExtensionTips, entry => { - if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) { - return; - } - if (important !== !!entry.value.important) { - return; - } - const exeName = entry.key; - if (platform === 'win32') { - let windowsPath = entry.value['windowsPath']; - if (!windowsPath || typeof windowsPath !== 'string') { - return; - } - windowsPath = windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!) - .replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!) - .replace('%ProgramFiles%', processEnv['ProgramFiles']!) - .replace('%APPDATA%', processEnv['APPDATA']!) - .replace('%WINDIR%', processEnv['WINDIR']!); - promises.push(findExecutable(exeName, entry.value, windowsPath)); - } else { - promises.push(findExecutable(exeName, entry.value, join('/usr/local/bin', exeName))); - promises.push(findExecutable(exeName, entry.value, join(this.environmentService.userHome, exeName))); - } - }); - - await Promise.all(promises); - } - - /** - * Fetch extensions used by others on the same workspace as recommendations from cache - */ - private fetchCachedDynamicWorkspaceRecommendations() { - if (this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER) { - return; - } - - const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations'; - let storedRecommendationsJson: { [key: string]: any } = {}; - try { - storedRecommendationsJson = JSON.parse(this.storageService.get(storageKey, StorageScope.WORKSPACE, '{}')); - } catch (e) { - this.storageService.remove(storageKey, StorageScope.WORKSPACE); - } - - if (Array.isArray(storedRecommendationsJson['recommendations']) - && isNumber(storedRecommendationsJson['timestamp']) - && storedRecommendationsJson['timestamp'] > 0 - && (Date.now() - storedRecommendationsJson['timestamp']) / milliSecondsInADay < 14) { - this._dynamicWorkspaceRecommendations = storedRecommendationsJson['recommendations']; - /* __GDPR__ - "dynamicWorkspaceRecommendations" : { - "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "cache" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('dynamicWorkspaceRecommendations', { count: this._dynamicWorkspaceRecommendations.length, cache: 1 }); - } - } - - /** - * Fetch extensions used by others on the same workspace as recommendations from recommendation service - */ - private fetchDynamicWorkspaceRecommendations(): Promise { - if (this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER - || !this.fileService.canHandleResource(this.contextService.getWorkspace().folders[0].uri) - || this._dynamicWorkspaceRecommendations.length - || !this._extensionsRecommendationsUrl) { - return Promise.resolve(undefined); - } - - const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations'; - const workspaceUri = this.contextService.getWorkspace().folders[0].uri; - return Promise.all([this.workspaceTagsService.getHashedRemotesFromUri(workspaceUri, false), this.workspaceTagsService.getHashedRemotesFromUri(workspaceUri, true)]).then(([hashedRemotes1, hashedRemotes2]) => { - const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []); - if (!hashedRemotes.length) { - return undefined; - } - - return this.requestService.request({ type: 'GET', url: this._extensionsRecommendationsUrl }, CancellationToken.None).then(context => { - if (context.res.statusCode !== 200) { - return Promise.resolve(undefined); - } - return asJson(context).then((result: { [key: string]: any } | null) => { - if (!result) { - return; - } - const allRecommendations: IDynamicWorkspaceRecommendations[] = Array.isArray(result['workspaceRecommendations']) ? result['workspaceRecommendations'] : []; - if (!allRecommendations.length) { - return; - } - - let foundRemote = false; - for (let i = 0; i < hashedRemotes.length && !foundRemote; i++) { - for (let j = 0; j < allRecommendations.length && !foundRemote; j++) { - if (Array.isArray(allRecommendations[j].remoteSet) && allRecommendations[j].remoteSet.indexOf(hashedRemotes[i]) > -1) { - foundRemote = true; - this._dynamicWorkspaceRecommendations = allRecommendations[j].recommendations.filter(id => this.isExtensionAllowedToBeRecommended(id)) || []; - this.storageService.store(storageKey, JSON.stringify({ - recommendations: this._dynamicWorkspaceRecommendations, - timestamp: Date.now() - }), StorageScope.WORKSPACE); - /* __GDPR__ - "dynamicWorkspaceRecommendations" : { - "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "cache" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('dynamicWorkspaceRecommendations', { count: this._dynamicWorkspaceRecommendations.length, cache: 0 }); - } - } - } - }); - }); - }); - } - - /** - * Fetch extension recommendations from currently running experiments - */ - private fetchExperimentalRecommendations() { - this.experimentService.getExperimentsByType(ExperimentActionType.AddToRecommendations).then(experiments => { - (experiments || []).forEach(experiment => { - const action = experiment.action; - if (action && experiment.state === ExperimentState.Run && action.properties && Array.isArray(action.properties.recommendations) && action.properties.recommendationReason) { - action.properties.recommendations.forEach((id: string) => { - this._experimentalRecommendations[id] = action.properties.recommendationReason; - }); - } - }); - }); - } - - //#endregion - - private isExtensionAllowedToBeRecommended(id: string): boolean { - return this._allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1; - } - -} diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 3228cb72d0c..0f5f66c4d63 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -9,12 +9,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionsLabel, ExtensionsChannelId, PreferencesLabel, IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServerService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -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'; @@ -42,30 +40,31 @@ import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/brow import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; 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'; +import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); -registerSingleton(IExtensionTipsService, ExtensionTipsService); +registerSingleton(IExtensionRecommendationsService, ExtensionRecommendationsService); 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( @@ -84,7 +83,8 @@ Registry.as(ViewContainerExtensions.ViewContainersRegis name: localize('extensions', "Extensions"), ctorDescriptor: new SyncDescriptor(ExtensionsViewPaneContainer), icon: 'codicon-extensions', - order: 4 + order: 4, + rejectAddedViews: true, }, ViewContainerLocation.Sidebar); @@ -278,7 +278,7 @@ CommandsRegistry.registerCommand({ const installed = await extensionManagementService.getInstalled(ExtensionType.User); const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); if (!extensionToUninstall) { - throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id)); + throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-dotnettools.csharp.", id)); } try { @@ -290,6 +290,30 @@ CommandsRegistry.registerCommand({ } }); +CommandsRegistry.registerCommand({ + id: 'workbench.extensions.search', + description: { + description: localize('workbench.extensions.search.description', "Search for a specific extension"), + args: [ + { + name: localize('workbench.extensions.search.arg.name', "Query to use in search"), + schema: { 'type': 'string' } + } + ] + }, + handler: async (accessor, query: string = '') => { + const viewletService = accessor.get(IViewletService); + const viewlet = await viewletService.openViewlet(VIEWLET_ID, true); + + if (!viewlet) { + return; + } + + (viewlet.getViewPaneContainer() as IExtensionsViewPaneContainer).search(query); + viewlet.focus(); + } +}); + // File menu registration MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { @@ -410,6 +434,33 @@ registerAction2(class extends Action2 { } }); +registerAction2(class extends Action2 { + + constructor() { + super({ + id: TOGGLE_IGNORE_EXTENSION_ACTION_ID, + title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Sync This Extension"), original: `Sync This Extension` }, + menu: { + id: MenuId.ExtensionContext, + group: '2_configure', + when: CONTEXT_SYNC_ENABLEMENT + }, + }); + } + + async run(accessor: ServicesAccessor, id: string) { + const configurationService = accessor.get(IConfigurationService); + const ignoredExtensions = [...configurationService.getValue('sync.ignoredExtensions')]; + const index = ignoredExtensions.findIndex(ignoredExtension => areSameExtensions({ id: ignoredExtension }, { id })); + if (index !== -1) { + ignoredExtensions.splice(index, 1); + } else { + ignoredExtensions.push(id); + } + return configurationService.updateValue('sync.ignoredExtensions', ignoredExtensions.length ? ignoredExtensions : undefined, ConfigurationTarget.USER); + } +}); + const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); class ExtensionsContributions implements IWorkbenchContribution { @@ -421,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 88e2d06be26..b3992dbc185 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -13,10 +13,10 @@ import * as json from 'vs/base/common/json'; import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; 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 } from 'vs/workbench/contrib/extensions/common/extensions'; +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 { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/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, IExtensionRecommendationsService, 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'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -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( @@ -660,9 +661,10 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { } } -export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, extension: IExtension | undefined | null): ExtensionAction[][] { +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) { @@ -672,7 +674,7 @@ export function getContextMenuActions(menuService: IMenuService, contextKeyServi const groups: ExtensionAction[][] = []; const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService); - menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => new MenuItemExtensionAction(action)))); + menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => instantiationService.createInstance(MenuItemExtensionAction, action)))); menu.dispose(); return groups; @@ -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); } } @@ -726,7 +732,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - getContextMenuActions(this.menuService, this.contextKeyService, this.extension).forEach(actions => groups.push(actions)); + getContextMenuActions(this.menuService, this.contextKeyService, this.instantiationService, this.extension).forEach(actions => groups.push(actions)); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); @@ -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 { @@ -754,11 +758,21 @@ export class ManageExtensionAction extends ExtensionDropDownAction { export class MenuItemExtensionAction extends ExtensionAction { - constructor(private readonly action: IAction) { + constructor( + private readonly action: IAction, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { super(action.id, action.label); } - update() { } + update() { + if (!this.extension) { + return; + } + if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) { + this.checked = !this.configurationService.getValue('sync.ignoredExtensions').some(id => areSameExtensions({ id }, this.extension!.identifier)); + } + } async run(): Promise { if (this.extension) { @@ -942,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( @@ -1151,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; @@ -1286,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)); @@ -1309,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, { @@ -1346,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)); @@ -1375,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, { @@ -1412,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'); } } @@ -1636,14 +1697,14 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions'; static readonly LABEL = localize('installWorkspaceRecommendedExtensions', "Install All Workspace Recommended Extensions"); - private _recommendations: IExtensionRecommendation[] = []; - get recommendations(): IExtensionRecommendation[] { return this._recommendations; } - set recommendations(recommendations: IExtensionRecommendation[]) { this._recommendations = recommendations; this.enabled = this._recommendations.length > 0; } + private _recommendations: string[] = []; + get recommendations(): string[] { return this._recommendations; } + set recommendations(recommendations: string[]) { this._recommendations = recommendations; this.enabled = this._recommendations.length > 0; } constructor( id: string = InstallWorkspaceRecommendedExtensionsAction.ID, label: string = InstallWorkspaceRecommendedExtensionsAction.LABEL, - recommendations: IExtensionRecommendation[], + recommendations: string[], @IViewletService private readonly viewletService: IViewletService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, @@ -1661,7 +1722,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { .then(viewlet => { viewlet.search('@recommended '); viewlet.focus(); - const names = this.recommendations.map(({ extensionId }) => extensionId); + const names = this.recommendations; return this.extensionWorkbenchService.queryGallery({ names, source: 'install-all-workspace-recommendations' }, CancellationToken.None).then(pager => { let installPromises: Promise[] = []; let model = new PagedModel(pager); @@ -1741,7 +1802,7 @@ export class IgnoreExtensionRecommendationAction extends Action { constructor( private readonly extension: IExtension, - @IExtensionTipsService private readonly extensionsTipsService: IExtensionTipsService, + @IExtensionRecommendationsService private readonly extensionsTipsService: IExtensionRecommendationsService, ) { super(IgnoreExtensionRecommendationAction.ID, 'Ignore Recommendation'); @@ -1764,7 +1825,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { constructor( private readonly extension: IExtension, - @IExtensionTipsService private readonly extensionsTipsService: IExtensionTipsService, + @IExtensionRecommendationsService private readonly extensionsTipsService: IExtensionRecommendationsService, ) { super(UndoIgnoreExtensionRecommendationAction.ID, 'Undo'); @@ -1779,7 +1840,6 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { } } - export class ShowRecommendedKeymapExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showRecommendedKeymapExtensions'; @@ -1929,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 @@ -1941,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 @@ -1955,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 @@ -1969,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 @@ -1983,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 @@ -1997,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 @@ -2173,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, @@ -2209,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, @@ -2400,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; @@ -2502,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."); @@ -2524,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; @@ -2603,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`; @@ -2659,7 +2746,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { if (server) { this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label); } else { - this.tooltip = localize('disabled because of extension kind', "This extension cannot be enabled in the remote server."); + this.tooltip = localize('disabled because of extension kind', "This extension has defined that it cannot run on the remote server"); } return; } @@ -2694,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, @@ -2719,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, @@ -2746,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, @@ -2771,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, @@ -3173,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..ab5dfc23c86 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,65 @@ 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'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; + +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 +160,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(); } @@ -209,7 +268,12 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree>{ + getAriaLabel(extensionData: IExtensionData): string { + return localize('extension-arialabel', "{0}. Press enter for extension details.", extensionData.extension.displayName); + } + } }, contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService ); @@ -218,7 +282,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 +310,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 2d091013be3..7e6f7a7642c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -10,7 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { isPromiseCanceledError, getErrorMessage } from 'vs/base/common/errors'; import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging'; import { SortBy, SortOrder, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionRecommendationsService, IExtensionRecommendation, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -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, ListResourceNavigator } 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,8 @@ 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'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -101,20 +101,20 @@ export class ExtensionsListView extends ViewPane { @IExtensionService private readonly extensionService: IExtensionService, @IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService, @IEditorService private readonly editorService: IEditorService, - @IExtensionTipsService protected tipsService: IExtensionTipsService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @IExtensionRecommendationsService protected tipsService: IExtensionRecommendationsService, + @ITelemetryService telemetryService: ITelemetryService, @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 private readonly contextKeyService: IContextKeyService, + @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); + super({ ...(options as IViewPaneOptions), showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.server = options.server; } @@ -141,19 +141,24 @@ export class ExtensionsListView extends ViewPane { multipleSelectionSupport: false, setRowLineHeight: false, horizontalScrolling: false, + accessibilityProvider: >{ + getAriaLabel(extension: IExtension | null): string { + return extension ? localize('extension-arialabel', "{0}. Press enter for extension details.", extension.displayName) : ''; + } + }, overrideStyles: { listBackground: SIDE_BAR_BACKGROUND } }); 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(new ListResourceNavigator(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 +237,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()]; @@ -247,7 +250,7 @@ export class ExtensionsListView extends ViewPane { getActions: () => actions.slice(0, actions.length - 1) }); } else if (e.element) { - const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), e.element); + const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = e.element!)); let actions: IAction[] = []; for (const menuActions of groups) { @@ -750,16 +753,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 +776,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()) ] }); @@ -876,12 +879,11 @@ export class ServerExtensionsView extends ExtensionsListView { @IInstantiationService instantiationService: IInstantiationService, @IExtensionService extensionService: IExtensionService, @IEditorService editorService: IEditorService, - @IExtensionTipsService tipsService: IExtensionTipsService, + @IExtensionRecommendationsService tipsService: IExtensionRecommendationsService, @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IExperimentService experimentService: IExperimentService, - @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, @@ -889,9 +891,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))); } @@ -1026,7 +1029,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { private async setRecommendationsToInstall(): Promise { const recommendations = await this.getRecommendationsToInstall(); if (this.installAllAction) { - this.installAllAction.recommendations = recommendations; + this.installAllAction.recommendations = recommendations.map(({ extensionId }) => extensionId); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index a885d7ec525..8a7c80af7c1 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 { IExtensionRecommendationsService, 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; @@ -158,12 +159,7 @@ export class TooltipWidget extends ExtensionWidget { } render(): void { - this.parent.title = ''; - this.parent.removeAttribute('aria-label'); this.parent.title = this.getTooltip(); - if (this.extension) { - this.parent.setAttribute('aria-label', localize('extension-arialabel', "{0}. Press enter for extension details.", this.extension.displayName)); - } } private getTooltip(): string { @@ -197,17 +193,16 @@ export class RecommendationWidget extends ExtensionWidget { constructor( private parent: HTMLElement, @IThemeService private readonly themeService: IThemeService, - @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService + @IExtensionRecommendationsService private readonly extensionRecommendationsService: IExtensionRecommendationsService ) { super(); this.render(); this._register(toDisposable(() => this.clear())); - this._register(this.extensionTipsService.onRecommendationChange(() => this.render())); + this._register(this.extensionRecommendationsService.onRecommendationChange(() => this.render())); } private clear(): void { this.tooltip = ''; - this.parent.setAttribute('aria-label', this.extension ? localize('viewExtensionDetailsAria', "{0}. Press enter for extension details.", this.extension.displayName) : ''); if (this.element) { this.parent.removeChild(this.element); } @@ -220,19 +215,19 @@ export class RecommendationWidget extends ExtensionWidget { if (!this.extension) { return; } - const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); + const extRecommendations = this.extensionRecommendationsService.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 +280,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 +291,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 +310,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/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts new file mode 100644 index 00000000000..af198f44531 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -0,0 +1,309 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { ExtensionRecommendationSource, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { forEach, IStringDictionary } from 'vs/base/common/collections'; +import { ITextModel } from 'vs/editor/common/model'; +import { Schemas } from 'vs/base/common/network'; +import { extname } from 'vs/base/common/resources'; +import { match } from 'vs/base/common/glob'; +import { URI } from 'vs/base/common/uri'; +import { MIME_UNKNOWN, guessMimeTypes } from 'vs/base/common/mime'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { setImmediate } from 'vs/base/common/platform'; + +type FileExtensionSuggestionClassification = { + userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + fileExtension: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; +}; + +const recommendationsStorageKey = 'extensionsAssistant/recommendations'; +const searchMarketplace = localize('searchMarketplace', "Search Marketplace"); +const milliSecondsInADay = 1000 * 60 * 60 * 24; +const processedFileExtensions: string[] = []; + +export class FileBasedRecommendations extends ExtensionRecommendations { + + private readonly extensionTips: IStringDictionary = Object.create(null); + private readonly importantExtensionTips: IStringDictionary<{ name: string; pattern: string; isExtensionPack?: boolean }> = Object.create(null); + + private fileBasedRecommendationsByPattern: IStringDictionary = Object.create(null); + private fileBasedRecommendations: IStringDictionary<{ recommendedTime: number, sources: ExtensionRecommendationSource[] }> = Object.create(null); + + get recommendations(): ReadonlyArray { + const recommendations: ExtensionRecommendation[] = []; + Object.keys(this.fileBasedRecommendations) + .sort((a, b) => { + if (this.fileBasedRecommendations[a].recommendedTime === this.fileBasedRecommendations[b].recommendedTime) { + if (this.importantExtensionTips[a]) { + return -1; + } + if (this.importantExtensionTips[b]) { + return 1; + } + } + return this.fileBasedRecommendations[a].recommendedTime > this.fileBasedRecommendations[b].recommendedTime ? -1 : 1; + }) + .forEach(extensionId => { + for (const source of this.fileBasedRecommendations[extensionId].sources) { + recommendations.push({ + extensionId, + source, + reason: { + reasonId: ExtensionRecommendationReason.File, + reasonText: localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened.") + } + }); + } + }); + return recommendations; + } + + constructor( + isExtensionAllowedToBeRecommended: (extensionId: string) => boolean, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionService private readonly extensionService: IExtensionService, + @IViewletService private readonly viewletService: IViewletService, + @IModelService private readonly modelService: IModelService, + @IProductService productService: IProductService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @INotificationService notificationService: INotificationService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + ) { + super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService); + + if (productService.extensionTips) { + forEach(productService.extensionTips, ({ key, value }) => this.extensionTips[key.toLowerCase()] = value); + } + if (productService.extensionImportantTips) { + forEach(productService.extensionImportantTips, ({ key, value }) => this.importantExtensionTips[key.toLowerCase()] = value); + } + } + + protected async doActivate(): Promise { + const allRecommendations: string[] = []; + + // group extension recommendations by pattern, like {**/*.md} -> [ext.foo1, ext.bar2] + forEach(this.extensionTips, ({ key: extensionId, value: pattern }) => { + const ids = this.fileBasedRecommendationsByPattern[pattern] || []; + ids.push(extensionId); + this.fileBasedRecommendationsByPattern[pattern] = ids; + allRecommendations.push(extensionId); + }); + forEach(this.importantExtensionTips, ({ key: extensionId, value }) => { + const ids = this.fileBasedRecommendationsByPattern[value.pattern] || []; + ids.push(extensionId); + this.fileBasedRecommendationsByPattern[value.pattern] = ids; + allRecommendations.push(extensionId); + }); + + const cachedRecommendations = this.getCachedRecommendations(); + const now = Date.now(); + // Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore + forEach(cachedRecommendations, ({ key, value }) => { + const diff = (now - value) / milliSecondsInADay; + if (diff <= 7 && allRecommendations.indexOf(key) > -1) { + this.fileBasedRecommendations[key] = { recommendedTime: value, sources: ['cached'] }; + } + }); + + this._register(this.modelService.onModelAdded(this.promptRecommendationsForModel, this)); + this.modelService.getModels().forEach(model => this.promptRecommendationsForModel(model)); + } + + /** + * Prompt the user to either install the recommended extension for the file type in the current editor model + * or prompt to search the marketplace if it has extensions that can support the file type + */ + private promptRecommendationsForModel(model: ITextModel): void { + const uri = model.uri; + const supportedSchemes = [Schemas.untitled, Schemas.file, Schemas.vscodeRemote]; + if (!uri || supportedSchemes.indexOf(uri.scheme) === -1) { + return; + } + + let fileExtension = extname(uri); + if (fileExtension) { + if (processedFileExtensions.indexOf(fileExtension) > -1) { + return; + } + processedFileExtensions.push(fileExtension); + } + + // re-schedule this bit of the operation to be off the critical path - in case glob-match is slow + setImmediate(() => this.promptRecommendations(uri, fileExtension)); + } + + private async promptRecommendations(uri: URI, fileExtension: string): Promise { + const recommendationsToPrompt: string[] = []; + forEach(this.fileBasedRecommendationsByPattern, ({ key: pattern, value: extensionIds }) => { + if (match(pattern, uri.toString())) { + for (const extensionId of extensionIds) { + // Add to recommendation to prompt if it is an important tip + if (this.importantExtensionTips[extensionId]) { + recommendationsToPrompt.push(extensionId); + } + // Update file based recommendations + const filedBasedRecommendation = this.fileBasedRecommendations[extensionId] || { recommendedTime: Date.now(), sources: [] }; + filedBasedRecommendation.recommendedTime = Date.now(); + if (!filedBasedRecommendation.sources.some(s => s instanceof URI && s.toString() === uri.toString())) { + filedBasedRecommendation.sources.push(uri); + } + this.fileBasedRecommendations[extensionId.toLowerCase()] = filedBasedRecommendation; + } + } + }); + + this.storeCachedRecommendations(); + + if (this.hasToIgnoreRecommendationNotifications()) { + return; + } + + const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + if (await this.promptRecommendedExtensionForFileType(recommendationsToPrompt, installed)) { + return; + } + + if (fileExtension) { + fileExtension = fileExtension.substr(1); // Strip the dot + } + if (!fileExtension) { + return; + } + + await this.extensionService.whenInstalledExtensionsRegistered(); + const mimeTypes = guessMimeTypes(uri); + if (mimeTypes.length !== 1 || mimeTypes[0] !== MIME_UNKNOWN) { + return; + } + + this.promptRecommendedExtensionForFileExtension(fileExtension, installed); + } + + private async promptRecommendedExtensionForFileType(recommendations: string[], installed: ILocalExtension[]): Promise { + + recommendations = this.filterIgnoredOrNotAllowed(recommendations); + if (recommendations.length === 0) { + return false; + } + + recommendations = this.filterInstalled(recommendations, installed); + if (recommendations.length === 0) { + return false; + } + + const extensionId = recommendations[0]; + const entry = this.importantExtensionTips[extensionId]; + if (!entry) { + return false; + } + const extensionName = entry.name; + let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", extensionName); + if (entry.isExtensionPack) { + message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", extensionName); + } + + this.promptImportantExtensionInstallNotification(extensionId, message); + return true; + } + + private async promptRecommendedExtensionForFileExtension(fileExtension: string, installed: ILocalExtension[]): Promise { + const fileExtensionSuggestionIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]')); + if (fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1) { + return; + } + + const text = `ext:${fileExtension}`; + const pager = await this.extensionsWorkbenchService.queryGallery({ text, pageSize: 100 }, CancellationToken.None); + if (pager.firstPage.length === 0) { + return; + } + + const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); + if (pager.firstPage.some(e => installedExtensionsIds.has(e.identifier.id.toLowerCase()))) { + return; + } + + this.notificationService.prompt( + Severity.Info, + localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension), + [{ + label: searchMarketplace, + run: () => { + this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension }); + this.viewletService.openViewlet('workbench.view.extensions', true) + .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) + .then(viewlet => { + viewlet.search(`ext:${fileExtension}`); + viewlet.focus(); + }); + } + }, { + label: localize('dontShowAgainExtension', "Don't Show Again for '.{0}' files", fileExtension), + run: () => { + fileExtensionSuggestionIgnoreList.push(fileExtension); + this.storageService.store( + 'extensionsAssistant/fileExtensionsSuggestionIgnore', + JSON.stringify(fileExtensionSuggestionIgnoreList), + StorageScope.GLOBAL + ); + this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension }); + } + }], + { + sticky: true, + onCancel: () => { + this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension }); + } + } + ); + } + + private filterInstalled(recommendationsToSuggest: string[], installed: ILocalExtension[]): string[] { + const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); + return recommendationsToSuggest.filter(id => !installedExtensionsIds.has(id.toLowerCase())); + } + + private getCachedRecommendations(): IStringDictionary { + let storedRecommendations = JSON.parse(this.storageService.get(recommendationsStorageKey, StorageScope.GLOBAL, '[]')); + if (Array.isArray(storedRecommendations)) { + storedRecommendations = storedRecommendations.reduce((result, id) => { result[id] = Date.now(); return result; }, >{}); + } + const result: IStringDictionary = {}; + forEach(storedRecommendations, ({ key, value }) => { + if (typeof value === 'number') { + result[key.toLowerCase()] = value; + } + }); + return result; + } + + private storeCachedRecommendations(): void { + const storedRecommendations: IStringDictionary = {}; + forEach(this.fileBasedRecommendations, ({ key, value }) => storedRecommendations[key] = value.recommendedTime); + this.storageService.store(recommendationsStorageKey, JSON.stringify(storedRecommendations), StorageScope.GLOBAL); + } +} + diff --git a/src/vs/workbench/contrib/extensions/browser/keymapRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/keymapRecommendations.ts new file mode 100644 index 00000000000..4bfefe4941c --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/keymapRecommendations.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + +export class KeymapRecommendations extends ExtensionRecommendations { + + private _recommendations: ExtensionRecommendation[] = []; + get recommendations(): ReadonlyArray { return this._recommendations; } + + constructor( + isExtensionAllowedToBeRecommended: (extensionId: string) => boolean, + @IProductService private readonly productService: IProductService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @INotificationService notificationService: INotificationService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + ) { + super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService); + } + + protected async doActivate(): Promise { + if (this.productService.keymapExtensionTips) { + this._recommendations = this.productService.keymapExtensionTips.map(extensionId => ({ + extensionId: extensionId.toLowerCase(), + source: 'application', + reason: { + reasonId: ExtensionRecommendationReason.Application, + reasonText: '' + } + })); + } + } + +} + 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..b1ce2490802 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -4,14 +4,29 @@ *--------------------------------------------------------------------------------------------*/ .monaco-action-bar .action-item .action-label.extension-action { - padding: 0 5px; - outline-offset: 2px; line-height: initial; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.monaco-action-bar .action-item .action-label.extension-action.label { + padding: 0 5px; + outline-offset: 2px; +} + +.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 { + padding: 0 2px; + height: 18px; + width: 16px; +} + .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 +35,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 +47,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 +57,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..b20458a44b8 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -135,6 +135,11 @@ padding-left: 14px; } +.extension-editor > .header > .details > .subtitle .version { + font-size: 90%; + font-style: italic; +} + .extension-editor > .header > .details > .description { margin-top: 10px; white-space: nowrap; @@ -154,16 +159,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 +204,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 +212,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 +273,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 +490,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 8e62f3a97be..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,231 +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; -} - -.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/browser/workspaceRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts new file mode 100644 index 00000000000..a45aa83c340 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/workspaceRecommendations.ts @@ -0,0 +1,258 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EXTENSION_IDENTIFIER_PATTERN, IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { distinct, flatten, coalesce } from 'vs/base/common/arrays'; +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IExtensionsConfigContent, ExtensionRecommendationSource, ExtensionRecommendationReason, IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { parse } from 'vs/base/common/json'; +import { EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { InstallWorkspaceRecommendedExtensionsAction, ShowRecommendedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; + +type ExtensionWorkspaceRecommendationsNotificationClassification = { + userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + +const choiceNever = localize('neverShowAgain', "Don't Show Again"); +const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; + +export class WorkspaceRecommendations extends ExtensionRecommendations { + + private _recommendations: ExtensionRecommendation[] = []; + get recommendations(): ReadonlyArray { return this._recommendations; } + + private _ignoredRecommendations: string[] = []; + get ignoredRecommendations(): ReadonlyArray { return this._ignoredRecommendations; } + + constructor( + isExtensionAllowedToBeRecommended: (extensionId: string) => boolean, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @ILogService private readonly logService: ILogService, + @IFileService private readonly fileService: IFileService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @INotificationService notificationService: INotificationService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + ) { + super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService); + } + + protected async doActivate(): Promise { + await this.fetch(); + this._register(this.contextService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e))); + this.promptWorkspaceRecommendations(); + } + + /** + * Parse all extensions.json files, fetch workspace recommendations, filter out invalid and unwanted ones + */ + private async fetch(): Promise { + + const extensionsConfigBySource = await this.fetchExtensionsConfigBySource(); + + const { invalidRecommendations, message } = await this.validateExtensions(extensionsConfigBySource.map(({ contents }) => contents)); + if (invalidRecommendations.length) { + this.notificationService.warn(`The below ${invalidRecommendations.length} extension(s) in workspace recommendations have issues:\n${message}`); + } + + this._ignoredRecommendations = []; + + for (const extensionsConfig of extensionsConfigBySource) { + for (const unwantedRecommendation of extensionsConfig.contents.unwantedRecommendations) { + if (invalidRecommendations.indexOf(unwantedRecommendation) === -1) { + this._ignoredRecommendations.push(unwantedRecommendation); + } + } + for (const extensionId of extensionsConfig.contents.recommendations) { + if (invalidRecommendations.indexOf(extensionId) === -1) { + this._recommendations.push({ + extensionId, + source: extensionsConfig.source, + reason: { + reasonId: ExtensionRecommendationReason.Workspace, + reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.") + } + }); + } + } + } + } + + private async promptWorkspaceRecommendations(): Promise { + const allowedRecommendations = this.recommendations.filter(rec => this.isExtensionAllowedToBeRecommended(rec.extensionId)); + + if (allowedRecommendations.length === 0 || this.hasToIgnoreWorkspaceRecommendationNotifications()) { + return; + } + + let installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + installed = installed.filter(l => this.extensionEnablementService.getEnablementState(l) !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind + const recommendations = allowedRecommendations.filter(({ extensionId }) => installed.every(local => !areSameExtensions({ id: extensionId }, local.identifier))); + + if (!recommendations.length) { + return; + } + + return new Promise(c => { + this.notificationService.prompt( + Severity.Info, + localize('workspaceRecommended', "This workspace has extension recommendations."), + [{ + label: localize('installAll', "Install All"), + run: () => { + this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }); + const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All"), recommendations.map(({ extensionId }) => extensionId)); + installAllAction.run(); + installAllAction.dispose(); + c(undefined); + } + }, { + label: localize('showRecommendations', "Show Recommendations"), + run: () => { + this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'show' }); + const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); + showAction.run(); + showAction.dispose(); + c(undefined); + } + }, { + label: choiceNever, + isSecondary: true, + run: () => { + this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' }); + this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE); + c(undefined); + } + }], + { + sticky: true, + onCancel: () => { + this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' }); + c(undefined); + } + } + ); + }); + } + + private async fetchExtensionsConfigBySource(): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource }[]> { + const workspace = this.contextService.getWorkspace(); + const result = await Promise.all([ + this.resolveWorkspaceExtensionConfig(workspace), + ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder)) + ]); + return coalesce(result); + } + + private async resolveWorkspaceExtensionConfig(workspace: IWorkspace): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource } | null> { + try { + if (workspace.configuration) { + const content = await this.fileService.readFile(workspace.configuration); + const extensionsConfigContent = parse(content.value.toString())['extensions']; + const contents = this.parseExtensionConfig(extensionsConfigContent); + if (contents) { + return { contents, source: workspace }; + } + } + } catch (e) { /* Ignore */ } + return null; + } + + private async resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource } | null> { + try { + const content = await this.fileService.readFile(workspaceFolder.toResource(EXTENSIONS_CONFIG)); + const extensionsConfigContent = parse(content.value.toString()); + const contents = this.parseExtensionConfig(extensionsConfigContent); + if (contents) { + return { contents, source: workspaceFolder }; + } + } catch (e) { /* ignore */ } + return null; + } + + private async validateExtensions(contents: IExtensionsConfigContent[]): Promise<{ validRecommendations: string[], invalidRecommendations: string[], message: string }> { + + const validExtensions: string[] = []; + const invalidExtensions: string[] = []; + const extensionsToQuery: string[] = []; + let message = ''; + + const allRecommendations = distinct(flatten(contents.map(({ recommendations }) => recommendations || []))); + const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); + for (const extensionId of allRecommendations) { + if (regEx.test(extensionId)) { + extensionsToQuery.push(extensionId); + } else { + invalidExtensions.push(extensionId); + message += `${extensionId} (bad format) Expected: .\n`; + } + } + + if (extensionsToQuery.length) { + try { + const queryResult = await this.galleryService.query({ names: extensionsToQuery, pageSize: extensionsToQuery.length }, CancellationToken.None); + const extensions = queryResult.firstPage.map(extension => extension.identifier.id.toLowerCase()); + + for (const extensionId of extensionsToQuery) { + if (extensions.indexOf(extensionId) === -1) { + invalidExtensions.push(extensionId); + message += `${extensionId} (not found in marketplace)\n`; + } else { + validExtensions.push(extensionId); + } + } + + } catch (e) { + this.logService.warn('Error querying extensions gallery', e); + } + } + + return { validRecommendations: validExtensions, invalidRecommendations: invalidExtensions, message }; + } + + private async onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): Promise { + if (event.added.length) { + const oldWorkspaceRecommended = this._recommendations; + await this.activate(); + // Suggest only if at least one of the newly added recommendations was not suggested before + if (this._recommendations.some(current => oldWorkspaceRecommended.every(old => current.extensionId !== old.extensionId))) { + this.promptWorkspaceRecommendations(); + } + } + } + + private parseExtensionConfig(extensionsConfigContent: IExtensionsConfigContent | undefined): IExtensionsConfigContent | null { + if (extensionsConfigContent) { + return { + recommendations: distinct((extensionsConfigContent.recommendations || []).map(e => e.toLowerCase())), + unwantedRecommendations: distinct((extensionsConfigContent.unwantedRecommendations || []).map(e => e.toLowerCase())) + }; + } + return null; + } + + private hasToIgnoreWorkspaceRecommendationNotifications(): boolean { + return this.hasToIgnoreRecommendationNotifications() || this.storageService.getBoolean(ignoreWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, false); + } +} + diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 1d64d4abe01..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; } @@ -140,3 +140,5 @@ export class ExtensionContainers extends Disposable { } } } + +export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension'; diff --git a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts index 48daaa1d61e..1fd19bffb54 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsInput.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts @@ -13,8 +13,15 @@ export class ExtensionsInput extends EditorInput { static readonly ID = 'workbench.extensions.input2'; get extension(): IExtension { return this._extension; } + get resource() { + return URI.from({ + scheme: 'extension', + path: this.extension.identifier.id + }); + } + constructor( - private _extension: IExtension, + private readonly _extension: IExtension ) { super(); } @@ -49,11 +56,4 @@ export class ExtensionsInput extends EditorInput { supportsSplitEditor(): boolean { return false; } - - getResource(): URI { - return URI.from({ - scheme: 'extension', - path: this.extension.identifier.id - }); - } } diff --git a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts index 3908cd44456..f14ffc84eaf 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts @@ -10,7 +10,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -28,7 +28,7 @@ export class KeymapExtensions extends Disposable implements IWorkbenchContributi constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IExtensionTipsService private readonly tipsService: IExtensionTipsService, + @IExtensionRecommendationsService private readonly tipsService: IExtensionRecommendationsService, @ILifecycleService lifecycleService: ILifecycleService, @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -122,7 +122,7 @@ export async function getInstalledExtensions(accessor: ServicesAccessor): Promis }); } -export function isKeymapExtension(tipsService: IExtensionTipsService, extension: IExtensionStatus): boolean { +export function isKeymapExtension(tipsService: IExtensionRecommendationsService, extension: IExtensionStatus): boolean { const cats = extension.local.manifest.categories; return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().some(({ extensionId }) => areSameExtensions({ id: extensionId }, extension.local.identifier)); } 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..0e082465137 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -18,11 +18,10 @@ 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 { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -66,7 +65,7 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ShowRuntimeEx class ExtensionsContributions implements IWorkbenchContribution { constructor( - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService workbenchEnvironmentService: INativeWorkbenchEnvironmentService ) { if (workbenchEnvironmentService.extensionsPath) { const openExtensionsFolderActionDescriptor = SyncActionDescriptor.create(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); @@ -106,8 +105,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 +117,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 +129,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 +141,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/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 871122fd967..94a29c5e229 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -7,7 +7,8 @@ import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { Schemas } from 'vs/base/common/network'; @@ -21,7 +22,7 @@ export class OpenExtensionsFolderAction extends Action { label: string, @IElectronService private readonly electronService: IElectronService, @IFileService private readonly fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService ) { super(id, label, undefined, true); } 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..6613d8c453e 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -47,6 +47,8 @@ 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'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -257,7 +259,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 +274,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 +298,15 @@ export class RuntimeExtensionsEditor extends BaseEditor { return { root, element, + icon, name, + version, actionbar, activationTime, profileTime, msgContainer, disposables, - elementDisposables: [] + elementDisposables: [], }; }, @@ -305,7 +316,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; @@ -415,7 +437,8 @@ export class RuntimeExtensionsEditor extends BaseEditor { horizontalScrolling: false, overrideStyles: { listBackground: editorBackground - } + }, + accessibilityProvider: new RuntimeExtensionsEditorAccessibilityProvider() }); this._list.splice(0, this._list.length, this._elements || undefined); @@ -668,3 +691,9 @@ export class SaveExtensionHostProfileAction extends Action { return writeFile(savePath, JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t')); } } + +class RuntimeExtensionsEditorAccessibilityProvider implements IListAccessibilityProvider { + getAriaLabel(element: IRuntimeExtension): string | null { + return element.description.name; + } +} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts index 14ffad68017..c4c1d43fe72 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts @@ -11,9 +11,10 @@ export class RuntimeExtensionsInput extends EditorInput { static readonly ID = 'workbench.runtimeExtensions.input'; - constructor() { - super(); - } + readonly resource = URI.from({ + scheme: 'runtime-extensions', + path: 'default' + }); getTypeId(): string { return RuntimeExtensionsInput.ID; @@ -37,11 +38,4 @@ export class RuntimeExtensionsInput extends EditorInput { supportsSplitEditor(): boolean { return false; } - - getResource(): URI { - return URI.from({ - scheme: 'runtime-extensions', - path: 'default' - }); - } } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts similarity index 69% rename from src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index 5242e47633c..ef9b59e46c6 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -12,17 +12,17 @@ import * as uuid from 'vs/base/common/uuid'; import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs'; import { IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; 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 } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService, TestStorageService } 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'; @@ -48,11 +48,16 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { FileService } from 'vs/platform/files/common/fileService'; -import { NullLogService } from 'vs/platform/log/common/log'; +import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; +import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; +import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService'; +import { NoOpWorkspaceTagsService } from 'vs/workbench/contrib/tags/browser/workspaceTagsService'; +import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; +import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { @@ -168,11 +173,11 @@ function aGalleryExtension(name: string, properties: any = {}, galleryExtensionP return galleryExtension; } -suite('ExtensionsTipsService Test', () => { +suite('ExtensionRecommendationsService Test', () => { let workspaceService: IWorkspaceContextService; let instantiationService: TestInstantiationService; let testConfigurationService: TestConfigurationService; - let testObject: ExtensionTipsService; + let testObject: ExtensionRecommendationsService; let parentResource: string; let installEvent: Emitter, didInstallEvent: Emitter, @@ -202,29 +207,31 @@ suite('ExtensionsTipsService Test', () => { instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IURLService, URLService); - instantiationService.set(IProductService, { - ...productService, - ...{ - extensionTips: { - 'ms-vscode.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', - 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}', - 'lukehoban.Go': '**/*.go' + instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService()); + instantiationService.stub(IStorageService, new TestStorageService()); + instantiationService.stub(ILogService, new NullLogService()); + instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); + instantiationService.stub(IProductService, >{ + extensionTips: { + 'ms-dotnettools.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', + 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}', + 'lukehoban.Go': '**/*.go' + }, + extensionImportantTips: { + 'ms-python.python': { + 'name': 'Python', + 'pattern': '{**/*.py}' }, - extensionImportantTips: { - 'ms-python.python': { - 'name': 'Python', - 'pattern': '{**/*.py}' - }, - 'ms-vscode.PowerShell': { - 'name': 'PowerShell', - 'pattern': '{**/*.ps,**/*.ps1}' - } + 'ms-vscode.PowerShell': { + 'name': 'PowerShell', + 'pattern': '{**/*.ps,**/*.ps1}' } } }); experimentService = instantiationService.createInstance(TestExperimentService); instantiationService.stub(IExperimentService, experimentService); + instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); onModelAddedEvent = new Emitter(); }); @@ -253,7 +260,6 @@ suite('ExtensionsTipsService Test', () => { instantiationService.stub(INotificationService, new TestNotificationService2()); testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false, showRecommendationsOnlyOnDemand: false }); - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c: boolean) => c, store: () => { } }); instantiationService.stub(IModelService, { getModels(): any { return []; }, onModelAdded: onModelAddedEvent.event @@ -261,7 +267,7 @@ suite('ExtensionsTipsService Test', () => { }); teardown(done => { - (testObject).dispose(); + (testObject).dispose(); if (parentResource) { rimraf(parentResource, RimRafMode.MOVE).then(done, done); } else { @@ -295,7 +301,7 @@ suite('ExtensionsTipsService Test', () => { function testNoPromptForValidRecommendations(recommendations: string[]) { return setUpFolderWorkspace('myFolder', recommendations).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { assert.equal(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length); assert.ok(!prompted); @@ -305,7 +311,7 @@ suite('ExtensionsTipsService Test', () => { function testNoPromptOrRecommendationsForValidRecommendations(recommendations: string[]) { return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); assert.ok(!prompted); return testObject.getWorkspaceRecommendations().then(() => { @@ -315,7 +321,7 @@ suite('ExtensionsTipsService Test', () => { }); } - test('ExtensionTipsService: No Prompt for valid workspace recommendations when galleryService is absent', () => { + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations when galleryService is absent', () => { const galleryQuerySpy = sinon.spy(); instantiationService.stub(IExtensionGalleryService, { query: galleryQuerySpy, isEnabled: () => false }); @@ -323,18 +329,18 @@ suite('ExtensionsTipsService Test', () => { .then(() => assert.ok(galleryQuerySpy.notCalled)); }); - test('ExtensionTipsService: No Prompt for valid workspace recommendations during extension development', () => { + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations during extension development', () => { instantiationService.stub(IEnvironmentService, { extensionDevelopmentLocationURI: [URI.file('/folder/file')] }); return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions); }); - test('ExtensionTipsService: No workspace recommendations or prompts when extensions.json has empty array', () => { + test('ExtensionRecommendationsService: No workspace recommendations or prompts when extensions.json has empty array', () => { return testNoPromptForValidRecommendations([]); }); - test('ExtensionTipsService: Prompt for valid workspace recommendations', () => { + test('ExtensionRecommendationsService: Prompt for valid workspace recommendations', () => { return setUpFolderWorkspace('myFolder', mockTestData.recommendedExtensions).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { const recommendations = Object.keys(testObject.getAllRecommendationsWithReason()); @@ -348,56 +354,46 @@ suite('ExtensionsTipsService Test', () => { }); }); - test('ExtensionTipsService: No Prompt for valid workspace recommendations if they are already installed', () => { + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if they are already installed', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal); return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); }); - test('ExtensionTipsService: No Prompt for valid workspace recommendations with casing mismatch if they are already installed', () => { + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations with casing mismatch if they are already installed', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal); return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions.map(x => x.toUpperCase())); }); - test('ExtensionTipsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set', () => { + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set', () => { testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: true }); return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); }); - test('ExtensionTipsService: No Prompt for valid workspace recommendations if showRecommendationsOnlyOnDemand is set', () => { + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if showRecommendationsOnlyOnDemand is set', () => { testConfigurationService.setUserConfiguration(ConfigurationKey, { showRecommendationsOnlyOnDemand: true }); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { - assert.equal(Object.keys(testObject.getAllRecommendationsWithReason()).length, 0); assert.ok(!prompted); }); }); }); - test('ExtensionTipsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => { - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => c, getBoolean: (a: string, b: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c }); + test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => { + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); }); - test('ExtensionTipsService: No Recommendations of globally ignored recommendations', () => { - const storageGetterStub = (a: string, _: StorageScope, c?: string) => { - const storedRecommendations = '["ms-vscode.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]'; - const ignoredRecommendations = '["ms-vscode.csharp", "mockpublisher2.mockextension2"]'; // ignore a stored recommendation and a workspace recommendation. - if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; } - if (a === 'extensionsAssistant/ignored_recommendations') { return ignoredRecommendations; } - return c; - }; - - instantiationService.stub(IStorageService, >{ - get: storageGetterStub, - getBoolean: (a: string, _: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c - }); + test('ExtensionRecommendationsService: No Recommendations of globally ignored recommendations', () => { + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.GLOBAL); + instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.GLOBAL); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { const recommendations = testObject.getAllRecommendationsWithReason(); - assert.ok(!recommendations['ms-vscode.csharp']); // stored recommendation that has been globally ignored + assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been globally ignored assert.ok(recommendations['ms-python.python']); // stored recommendation assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been globally ignored @@ -405,19 +401,17 @@ suite('ExtensionsTipsService Test', () => { }); }); - test('ExtensionTipsService: No Recommendations of workspace ignored recommendations', () => { - const ignoredRecommendations = ['ms-vscode.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation. - const storedRecommendations = '["ms-vscode.csharp", "ms-python.python"]'; - instantiationService.stub(IStorageService, >{ - get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, - getBoolean: (a: string, _: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c - }); + test('ExtensionRecommendationsService: No Recommendations of workspace ignored recommendations', () => { + const ignoredRecommendations = ['ms-dotnettools.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation. + const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]'; + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { const recommendations = testObject.getAllRecommendationsWithReason(); - assert.ok(!recommendations['ms-vscode.csharp']); // stored recommendation that has been workspace ignored + assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been workspace ignored assert.ok(recommendations['ms-python.python']); // stored recommendation assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been workspace ignored @@ -425,51 +419,36 @@ suite('ExtensionsTipsService Test', () => { }); }); - test('ExtensionTipsService: Able to retrieve collection of all ignored recommendations', () => { + test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', () => { - const storageGetterStub = (a: string, _: StorageScope, c?: string) => { - const storedRecommendations = '["ms-vscode.csharp", "ms-python.python"]'; - const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. - if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; } - if (a === 'extensionsAssistant/ignored_recommendations') { return globallyIgnoredRecommendations; } - return c; - }; - - const workspaceIgnoredRecommendations = ['ms-vscode.csharp']; // ignore a stored recommendation and a workspace recommendation. - instantiationService.stub(IStorageService, >{ - get: storageGetterStub, - getBoolean: (a: string, _: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c - }); + const workspaceIgnoredRecommendations = ['ms-dotnettools.csharp']; // ignore a stored recommendation and a workspace recommendation. + const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]'; + const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); + instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { const recommendations = testObject.getAllRecommendationsWithReason(); assert.ok(recommendations['ms-python.python']); assert.ok(!recommendations['mockpublisher2.mockextension2']); - assert.ok(!recommendations['ms-vscode.csharp']); + assert.ok(!recommendations['ms-dotnettools.csharp']); }); }); }); - test('ExtensionTipsService: Able to dynamically ignore/unignore global recommendations', () => { - const storageGetterStub = (a: string, _: StorageScope, c?: string) => { - const storedRecommendations = '["ms-vscode.csharp", "ms-python.python"]'; - const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. - if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; } - if (a === 'extensionsAssistant/ignored_recommendations') { return globallyIgnoredRecommendations; } - return c; - }; - - instantiationService.stub(IStorageService, >{ - get: storageGetterStub, - store: () => { }, - getBoolean: (a: string, _: StorageScope, c?: boolean) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c - }); + test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', () => { + const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]'; + const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); + instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { const recommendations = testObject.getAllRecommendationsWithReason(); assert.ok(recommendations['ms-python.python']); @@ -497,57 +476,51 @@ suite('ExtensionsTipsService Test', () => { }); test('test global extensions are modified and recommendation change event is fired when an extension is ignored', async () => { - const storageSetterTarget = sinon.spy(); const changeHandlerTarget = sinon.spy(); const ignoredExtensionId = 'Some.Extension'; - instantiationService.stub(IStorageService, >{ - get: (a: string, b: StorageScope, c?: boolean) => a === 'extensionsAssistant/ignored_recommendations' ? '["ms-vscode.vscode"]' : c, - getBoolean: (a: string, b: StorageScope, c: boolean) => c, - store: (...args: any[]) => { - storageSetterTarget(...args); - } - }); + + instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE); + instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.GLOBAL); await setUpFolderWorkspace('myFolder', []); - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); testObject.onRecommendationChange(changeHandlerTarget); testObject.toggleIgnoredRecommendation(ignoredExtensionId, true); await testObject.loadWorkspaceConfigPromise; assert.ok(changeHandlerTarget.calledOnce); - assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: 'Some.Extension', isRecommended: false })); - assert.ok(storageSetterTarget.calledWithExactly('extensionsAssistant/ignored_recommendations', `["ms-vscode.vscode","${ignoredExtensionId.toLowerCase()}"]`, StorageScope.GLOBAL)); + assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: ignoredExtensionId.toLowerCase(), isRecommended: false })); }); - test('ExtensionTipsService: Get file based recommendations from storage (old format)', () => { - const storedRecommendations = '["ms-vscode.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]'; - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c }); + test('ExtensionRecommendationsService: Get file based recommendations from storage (old format)', () => { + const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]'; + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); return setUpFolderWorkspace('myFolder', []).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { const recommendations = testObject.getFileBasedRecommendations(); assert.equal(recommendations.length, 2); - assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-vscode.csharp')); // stored recommendation that exists in product.extensionTips + assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips }); }); }); - test('ExtensionTipsService: Get file based recommendations from storage (new format)', () => { + test('ExtensionRecommendationsService: Get file based recommendations from storage (new format)', () => { const milliSecondsInADay = 1000 * 60 * 60 * 24; const now = Date.now(); const tenDaysOld = 10 * milliSecondsInADay; - const storedRecommendations = `{"ms-vscode.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`; - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c }); + const storedRecommendations = `{"ms-dotnettools.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`; + instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL); return setUpFolderWorkspace('myFolder', []).then(() => { - testObject = instantiationService.createInstance(ExtensionTipsService); + testObject = instantiationService.createInstance(ExtensionRecommendationsService); return testObject.loadWorkspaceConfigPromise.then(() => { const recommendations = testObject.getFileBasedRecommendations(); assert.equal(recommendations.length, 2); - assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-vscode.csharp')); // stored recommendation that exists in product.extensionTips + assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'lukehoban.Go')); //stored recommendation that is older than a week 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..2f030b7aed1 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 @@ -11,12 +11,12 @@ import * as ExtensionsActions from 'vs/workbench/contrib/extensions/browser/exte import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, InstallOperation + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, InstallOperation, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; +import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; @@ -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'; @@ -47,6 +47,12 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; +import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; +import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; +import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('ExtensionsActions Test', () => { @@ -71,6 +77,8 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IProgressService, ProgressService); + instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); + instantiationService.stub(IProductService, {}); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(ISharedProcessService, TestSharedProcessService); @@ -92,9 +100,12 @@ 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(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(IExperimentService, instantiationService.createInstance(TestExperimentService)); + instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); + instantiationService.set(IExtensionRecommendationsService, instantiationService.createInstance(ExtensionRecommendationsService)); instantiationService.stub(IURLService, URLService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); @@ -130,7 +141,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 +159,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 +226,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 +241,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 +256,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 +292,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 +301,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 +314,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 +330,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 +345,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 +362,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 +381,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 +397,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 +509,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 +524,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 +541,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 +559,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 +574,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 +591,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 +1560,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 +1586,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 +1619,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 +1650,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 +1867,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 +1913,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 +1939,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 +1972,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 +2003,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 +2223,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/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 7334f11625f..7a6ba18ada9 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 @@ -14,10 +14,9 @@ import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, SortBy } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionTipsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionRecommendationsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; @@ -27,7 +26,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,7 +45,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', () => { @@ -108,27 +108,34 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.stub(IExtensionTipsService, ExtensionTipsService); - instantiationService.stub(IURLService, URLService); - - instantiationService.stubPromise(IExtensionTipsService, 'getWorkspaceRecommendations', [ - { extensionId: workspaceRecommendationA.identifier.id }, - { extensionId: workspaceRecommendationB.identifier.id }]); - instantiationService.stub(IExtensionTipsService, 'getFileBasedRecommendations', [ - { extensionId: fileBasedRecommendationA.identifier.id }, - { extensionId: fileBasedRecommendationB.identifier.id }]); - instantiationService.stubPromise(IExtensionTipsService, 'getOtherRecommendations', [ - { extensionId: otherRecommendationA.identifier.id } - ]); const reasons: { [key: string]: any } = {}; reasons[workspaceRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace }; reasons[workspaceRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace }; reasons[fileBasedRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.File }; reasons[fileBasedRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.File }; reasons[otherRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Executable }; - - instantiationService.stub(IExtensionTipsService, 'getAllRecommendationsWithReason', reasons); - + instantiationService.stub(IExtensionRecommendationsService, >{ + getWorkspaceRecommendations() { + return Promise.resolve([ + { extensionId: workspaceRecommendationA.identifier.id }, + { extensionId: workspaceRecommendationB.identifier.id }]); + }, + getFileBasedRecommendations() { + return [ + { extensionId: fileBasedRecommendationA.identifier.id }, + { extensionId: fileBasedRecommendationB.identifier.id } + ]; + }, + getOtherRecommendations() { + return Promise.resolve([ + { extensionId: otherRecommendationA.identifier.id } + ]); + }, + getAllRecommendationsWithReason() { + return reasons; + } + }); + instantiationService.stub(IURLService, URLService); }); setup(async () => { @@ -137,6 +144,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..50f6317a41e 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 @@ -12,12 +12,12 @@ import { IExtensionsWorkbenchService, ExtensionState, AutoCheckUpdatesConfigurat import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, - DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation + DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; +import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; @@ -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,14 @@ 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'; +import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; +import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; +import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; suite('ExtensionsWorkbenchServiceTest', () => { @@ -62,6 +69,8 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); instantiationService.stub(IProgressService, ProgressService); + instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); + instantiationService.stub(IProductService, {}); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(IURLService, URLService); @@ -91,7 +100,10 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); + instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(IExperimentService, instantiationService.createInstance(TestExperimentService)); + instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); + instantiationService.set(IExtensionRecommendationsService, instantiationService.createInstance(ExtensionRecommendationsService)); instantiationService.stub(INotificationService, { prompt: () => null! }); }); 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/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts deleted file mode 100644 index ae100449e1f..00000000000 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ /dev/null @@ -1,391 +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 { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { URI } from 'vs/base/common/uri'; -import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; -import { ITextFileService, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { distinct, coalesce } from 'vs/base/common/arrays'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ResourceMap } from 'vs/base/common/map'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { timeout, RunOnceWorker } from 'vs/base/common/async'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; -import { Schemas } from 'vs/base/common/network'; - -export class FileEditorTracker extends Disposable implements IWorkbenchContribution { - - private readonly activeOutOfWorkspaceWatchers = new ResourceMap(); - - constructor( - @IEditorService private readonly editorService: IEditorService, - @ITextFileService private readonly textFileService: ITextFileService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IFileService private readonly fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IHostService private readonly hostService: IHostService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService - ) { - super(); - - this.onConfigurationUpdated(configurationService.getValue()); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Update editors from operation changes - this._register(this.fileService.onAfterOperation(e => this.onFileOperation(e))); - - // Update editors from disk changes - this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); - - // Ensure dirty text file and untitled models are always opened as editors - this._register(this.textFileService.files.onDidChangeDirty(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); - this._register(this.textFileService.files.onDidSaveError(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); - this._register(this.textFileService.untitled.onDidChangeDirty(r => this.ensureDirtyFilesAreOpenedWorker.work(r))); - - // Out of workspace file watchers - this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); - - // Update visible editors when focus is gained - this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChange(e))); - - // Configuration - this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); - - // Lifecycle - this.lifecycleService.onShutdown(this.dispose, this); - } - - //#region Handle deletes and moves in opened editors - - // Note: there is some duplication with the other file event handler below. Since we cannot always rely on the disk events - // carrying all necessary data in all environments, we also use the file operation events to make sure operations are handled. - // In any case there is no guarantee if the local event is fired first or the disk one. Thus, code must handle the case - // that the event ordering is random as well as might not carry all information needed. - private onFileOperation(e: FileOperationEvent): void { - - // Handle moves specially when file is opened - if (e.isOperation(FileOperation.MOVE)) { - this.handleMovedFileInOpenedFileEditors(e.resource, e.target.resource); - } - - // Handle deletes - if (e.isOperation(FileOperation.DELETE) || e.isOperation(FileOperation.MOVE)) { - this.handleDeletes(e.resource, false, e.target ? e.target.resource : undefined); - } - } - - private handleMovedFileInOpenedFileEditors(oldResource: URI, newResource: URI): void { - this.editorGroupService.groups.forEach(group => { - group.editors.forEach(editor => { - if (editor instanceof FileEditorInput) { - - // Update Editor if file (or any parent of the input) got renamed or moved - const resource = editor.getResource(); - if (isEqualOrParent(resource, oldResource)) { - let reopenFileResource: URI; - if (oldResource.toString() === resource.toString()) { - reopenFileResource = newResource; // file got moved - } else { - const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); - const index = this.getIndexOfPath(resource.path, oldResource.path, ignoreCase); - reopenFileResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved - } - - let encoding: string | undefined = undefined; - const model = this.textFileService.files.get(resource); - if (model) { - encoding = model.getEncoding(); - } - - this.editorService.replaceEditors([{ - editor: { resource }, - replacement: { - resource: reopenFileResource, - encoding, - options: { - preserveFocus: true, - pinned: group.isPinned(editor), - index: group.getIndexOfEditor(editor), - inactive: !group.isActive(editor), - viewState: this.getViewStateFor(oldResource, group) - } - }, - }], group); - } - } - }); - }); - } - - private getIndexOfPath(path: string, candidate: string, ignoreCase: boolean): number { - if (candidate.length > path.length) { - return -1; - } - - if (path === candidate) { - return 0; - } - - if (ignoreCase) { - path = path.toLowerCase(); - candidate = candidate.toLowerCase(); - } - - return path.indexOf(candidate); - } - - private getViewStateFor(resource: URI, group: IEditorGroup): IEditorViewState | undefined { - const editors = this.editorService.visibleControls; - - for (const editor of editors) { - if (editor?.input && editor.group === group) { - const editorResource = editor.input.getResource(); - if (editorResource && resource.toString() === editorResource.toString()) { - const control = editor.getControl(); - if (isCodeEditor(control)) { - return withNullAsUndefined(control.saveViewState()); - } - } - } - } - - return undefined; - } - - //#endregion - - //#region File Changes: Close editors of deleted files unless configured otherwise - - private closeOnFileDelete: boolean = false; - - private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { - if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') { - this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete; - } else { - this.closeOnFileDelete = false; // default - } - } - - private onFileChanges(e: FileChangesEvent): void { - if (e.gotDeleted()) { - this.handleDeletes(e, true); - } - } - - private handleDeletes(arg1: URI | FileChangesEvent, isExternal: boolean, movedTo?: URI): void { - const nonDirtyFileEditors = this.getNonDirtyFileEditors(); - nonDirtyFileEditors.forEach(async editor => { - const resource = editor.getResource(); - - // Handle deletes in opened editors depending on: - // - the user has not disabled the setting closeOnFileDelete - // - the file change is local or external - // - the input is not resolved (we need to dispose because we cannot restore otherwise since we do not have the contents) - if (this.closeOnFileDelete || !isExternal || !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 - // path but different casing. - if (movedTo && isEqualOrParent(resource, movedTo)) { - return; - } - - let matches = false; - if (arg1 instanceof FileChangesEvent) { - matches = arg1.contains(resource, FileChangeType.DELETED); - } else { - matches = isEqualOrParent(resource, arg1); - } - - if (!matches) { - return; - } - - // We have received reports of users seeing delete events even though the file still - // exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665). - // Since we do not want to close an editor without reason, we have to check if the - // file is really gone and not just a faulty file event. - // This only applies to external file events, so we need to check for the isExternal - // flag. - let exists = false; - if (isExternal) { - await timeout(100); - exists = await this.fileService.exists(resource); - } - - if (!exists && !editor.isDisposed()) { - editor.dispose(); - } else if (this.environmentService.verbose) { - console.warn(`File exists even though we received a delete event: ${resource.toString()}`); - } - } - }); - } - - private getNonDirtyFileEditors(): FileEditorInput[] { - const editors: FileEditorInput[] = []; - - this.editorService.editors.forEach(editor => { - if (editor instanceof FileEditorInput) { - if (!editor.isDirty()) { - editors.push(editor); - } - } else if (editor instanceof SideBySideEditorInput) { - const master = editor.master; - const details = editor.details; - - if (master instanceof FileEditorInput) { - if (!master.isDirty()) { - editors.push(master); - } - } - - if (details instanceof FileEditorInput) { - if (!details.isDirty()) { - editors.push(details); - } - } - } - }); - - return editors; - } - - //#endregion - - //#region Text File: Ensure every dirty text and untitled file is opened in an editor - - private readonly ensureDirtyFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyFilesAreOpened(units), 250)); - - private ensureDirtyFilesAreOpened(resources: URI[]): void { - this.doEnsureDirtyFilesAreOpened(distinct(resources.filter(resource => { - if (!this.textFileService.isDirty(resource)) { - return false; // resource must be dirty - } - - const model = this.textFileService.files.get(resource); - if (model?.hasState(ModelState.PENDING_SAVE)) { - 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 }))) { - return false; // model must not be opened already as file - } - - return true; - }), resource => resource.toString())); - } - - private doEnsureDirtyFilesAreOpened(resources: URI[]): void { - if (!resources.length) { - return; - } - - this.editorService.openEditors(resources.map(resource => ({ - resource, - options: { inactive: true, pinned: true, preserveFocus: true } - }))); - } - - //#endregion - - //#region Visible Editors Change: Install file watchers for out of workspace resources that became visible - - private onDidVisibleEditorsChange(): void { - const visibleOutOfWorkspaceResources = new ResourceMap(); - - for (const editor of this.editorService.visibleEditors) { - const resources = distinct(coalesce([ - toResource(editor, { supportSideBySide: SideBySideEditorChoice.MASTER }), - toResource(editor, { supportSideBySide: SideBySideEditorChoice.DETAILS }) - ]), resource => resource.toString()); - - for (const resource of resources) { - if (this.fileService.canHandleResource(resource) && !this.contextService.isInsideWorkspace(resource)) { - visibleOutOfWorkspaceResources.set(resource, resource); - } - } - } - - // Handle no longer visible out of workspace resources - this.activeOutOfWorkspaceWatchers.keys().forEach(resource => { - if (!visibleOutOfWorkspaceResources.get(resource)) { - dispose(this.activeOutOfWorkspaceWatchers.get(resource)); - this.activeOutOfWorkspaceWatchers.delete(resource); - } - }); - - // Handle newly visible out of workspace resources - visibleOutOfWorkspaceResources.forEach(resource => { - if (!this.activeOutOfWorkspaceWatchers.get(resource)) { - const disposable = this.fileService.watch(resource); - this.activeOutOfWorkspaceWatchers.set(resource, disposable); - } - }); - } - - //#endregion - - //#region Window Focus Change: Update visible code editors when focus is gained - - private onWindowFocusChange(focused: boolean): void { - if (focused) { - // the window got focus and we use this as a hint that files might have been changed outside - // of this window. since file events can be unreliable, we queue a load for models that - // are visible in any editor. since this is a fast operation in the case nothing has changed, - // we tolerate the additional work. - distinct( - coalesce(this.codeEditorService.listCodeEditors() - .map(codeEditor => { - const resource = codeEditor.getModel()?.uri; - if (!resource) { - return undefined; - } - - const model = this.textFileService.files.get(resource); - if (!model) { - return undefined; - } - - if (model.isDirty()) { - return undefined; - } - - return model; - })), - model => model.resource.toString() - ).forEach(model => model.load()); - } - } - - //#endregion - - dispose(): void { - super.dispose(); - - // Dispose remaining watchers if any - this.activeOutOfWorkspaceWatchers.forEach(disposable => dispose(disposable)); - this.activeOutOfWorkspaceWatchers.clear(); - } -} diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index aae80069097..856a58552a8 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -10,13 +10,13 @@ import { isValidBasename } from 'vs/base/common/extpath'; import { basename } from 'vs/base/common/resources'; import { Action } from 'vs/base/common/actions'; import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; -import { ITextFileEditorModel, ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -31,6 +31,7 @@ import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { MutableDisposable } from 'vs/base/common/lifecycle'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; /** * An implementation of editor for file system resources. @@ -49,28 +50,38 @@ export class TextFileEditor extends BaseTextEditor { @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService private readonly textFileService: ITextFileService, - @IExplorerService private readonly explorerService: IExplorerService + @IExplorerService private readonly explorerService: IExplorerService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { - super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); + super(TextFileEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService); this.updateRestoreViewStateConfiguration(); // Clear view state for deleted files - this._register(this.fileService.onFileChanges(e => this.onFilesChanged(e))); + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); + + // Move view state for moved files + this._register(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e))); } - private onFilesChanged(e: FileChangesEvent): void { + private onDidFilesChange(e: FileChangesEvent): void { const deleted = e.getDeleted(); if (deleted?.length) { this.clearTextEditorViewState(deleted.map(d => d.resource)); } } + private onDidRunOperation(e: FileOperationEvent): void { + if (e.operation === FileOperation.MOVE && e.target) { + this.moveTextEditorViewState(e.resource, e.target.resource); + } + } + protected handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void { super.handleConfigurationChangeEvent(configuration); @@ -78,7 +89,7 @@ export class TextFileEditor extends BaseTextEditor { } private updateRestoreViewStateConfiguration(): void { - this.restoreViewState = this.textResourceConfigurationService.getValue(undefined, 'workbench.editor.restoreViewState'); + this.restoreViewState = this.configurationService.getValue('workbench.editor.restoreViewState') ?? true /* default */; } getTitle(): string { @@ -135,14 +146,14 @@ export class TextFileEditor extends BaseTextEditor { return this.openAsBinary(input, options); } - const textFileModel = resolvedModel; + const textFileModel = resolvedModel; // Editor const textEditor = assertIsDefined(this.getControl()); textEditor.setModel(textFileModel.textEditorModel); // Always restore View State if any associated - const editorViewState = this.loadTextEditorViewState(input.getResource()); + const editorViewState = this.loadTextEditorViewState(input.resource); if (editorViewState) { textEditor.restoreViewState(editorViewState); } @@ -180,14 +191,14 @@ export class TextFileEditor extends BaseTextEditor { } // Offer to create a file from the error if we have a file not found and the name is valid - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && isValidBasename(basename(input.getResource()))) { + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && isValidBasename(basename(input.resource))) { throw createErrorWithActions(toErrorMessage(error), { actions: [ new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), undefined, true, async () => { - await this.textFileService.create(input.getResource()); + await this.textFileService.create(input.resource); return this.editorService.openEditor({ - resource: input.getResource(), + resource: input.resource, options: { pinned: true // new file gets pinned by default } @@ -227,10 +238,10 @@ export class TextFileEditor extends BaseTextEditor { await this.group.closeEditor(this.input); // Best we can do is to reveal the folder in the explorer - if (this.contextService.isInsideWorkspace(input.getResource())) { + if (this.contextService.isInsideWorkspace(input.resource)) { await this.viewletService.openViewlet(VIEWLET_ID); - this.explorerService.select(input.getResource(), true); + this.explorerService.select(input.resource, true); } } @@ -278,12 +289,12 @@ export class TextFileEditor extends BaseTextEditor { // If the user configured to not restore view state, we clear the view // state unless the editor is still opened in the group. if (!this.restoreViewState && (!this.group || !this.group.isOpened(input))) { - this.clearTextEditorViewState([input.getResource()], this.group); + this.clearTextEditorViewState([input.resource], this.group); } // Otherwise we save the view state to restore it later else if (!input.isDisposed()) { - this.saveTextEditorViewState(input.getResource()); + this.saveTextEditorViewState(input.resource); } } } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts new file mode 100644 index 00000000000..9d6d291ea17 --- /dev/null +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { URI } from 'vs/base/common/uri'; +import { ITextFileService, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { distinct, coalesce } from 'vs/base/common/arrays'; +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'; + +export class TextFileEditorTracker extends Disposable implements IWorkbenchContribution { + + constructor( + @IEditorService private readonly editorService: IEditorService, + @ITextFileService private readonly textFileService: ITextFileService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IHostService private readonly hostService: IHostService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Ensure dirty text file and untitled models are always opened as editors + this._register(this.textFileService.files.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource))); + this._register(this.textFileService.files.onDidSaveError(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource))); + this._register(this.textFileService.untitled.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource))); + + // Update visible text file editors when focus is gained + this._register(this.hostService.onDidChangeFocus(hasFocus => hasFocus ? this.reloadVisibleTextFileEditors() : undefined)); + + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); + } + + //#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), 50)); + + private ensureDirtyTextFilesAreOpened(resources: URI[]): void { + this.doEnsureDirtyTextFilesAreOpened(distinct(resources.filter(resource => { + if (!this.textFileService.isDirty(resource)) { + return false; // resource must be dirty + } + + const model = this.textFileService.files.get(resource); + if (model?.hasState(TextFileEditorModelState.PENDING_SAVE)) { + return false; // resource must not be pending to save + } + + if (this.editorService.isOpen({ resource })) { + return false; // model must not be opened already as file + } + + return true; + }), resource => resource.toString())); + } + + private doEnsureDirtyTextFilesAreOpened(resources: URI[]): void { + if (!resources.length) { + return; + } + + this.editorService.openEditors(resources.map(resource => ({ + resource, + options: { inactive: true, pinned: true, preserveFocus: true } + }))); + } + + //#endregion + + //#region Window Focus Change: Update visible code editors when focus is gained that have a known text file model + + private reloadVisibleTextFileEditors(): void { + // the window got focus and we use this as a hint that files might have been changed outside + // of this window. since file events can be unreliable, we queue a load for models that + // are visible in any editor. since this is a fast operation in the case nothing has changed, + // we tolerate the additional work. + distinct( + coalesce(this.codeEditorService.listCodeEditors() + .map(codeEditor => { + const resource = codeEditor.getModel()?.uri; + if (!resource) { + return undefined; + } + + const model = this.textFileService.files.get(resource); + if (!model || model.isDirty() || !model.isResolved()) { + return undefined; + } + + return model; + })), + model => model.resource.toString() + ).forEach(model => this.textFileService.files.resolve(model.resource, { reload: { async: true } })); + } + + //#endregion +} diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 5c214b0641d..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); @@ -64,7 +69,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa this._register(textModelService.registerTextModelContentProvider(CONFLICT_RESOLUTION_SCHEME, provider)); // Set as save error handler to service for text files - this.textFileService.saveErrorHandler = this; + this.textFileService.files.saveErrorHandler = this; this.registerListeners(); } @@ -81,10 +86,10 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa const activeInput = this.editorService.activeEditor; if (activeInput instanceof DiffEditorInput && activeInput.originalInput instanceof ResourceEditorInput && activeInput.modifiedInput instanceof FileEditorInput) { - const resource = activeInput.originalInput.getResource(); + const resource = activeInput.originalInput.resource; if (resource?.scheme === CONFLICT_RESOLUTION_SCHEME) { isActiveEditorSaveConflictResolution = true; - activeConflictResolutionResource = activeInput.modifiedInput.getResource(); + activeConflictResolutionResource = activeInput.modifiedInput.resource; } } @@ -100,7 +105,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa } } - onSaveError(error: any, model: ITextFileEditorModel): void { + onSaveError(error: unknown, model: ITextFileEditorModel): void { const fileOperationError = error as FileOperationError; const resource = model.resource; @@ -208,8 +213,8 @@ class ResolveConflictLearnMoreAction extends Action { super('workbench.files.action.resolveConflictLearnMore', nls.localize('learnMore', "Learn More")); } - run(): Promise { - return this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=868264')); + async run(): Promise { + await this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=868264')); } } @@ -221,13 +226,11 @@ class DoNotShowResolveConflictLearnMoreAction extends Action { super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', nls.localize('dontShowAgain', "Don't Show Again")); } - run(notification: IDisposable): Promise { + async run(notification: IDisposable): Promise { this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL); // Hide notification notification.dispose(); - - return Promise.resolve(); } } @@ -238,12 +241,16 @@ 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 { + async run(): Promise { if (!this.model.isDisposed()) { const resource = this.model.resource; const name = basename(resource); @@ -262,8 +269,6 @@ class ResolveSaveConflictAction extends Action { Event.once(handle.onDidClose)(() => dispose(actions.primary!)); pendingResolveSaveConflictMessages.push(handle); } - - return Promise.resolve(true); } } @@ -276,7 +281,7 @@ class SaveElevatedAction extends Action { super('workbench.files.action.saveElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo...")); } - run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ writeElevated: true, @@ -284,8 +289,6 @@ class SaveElevatedAction extends Action { reason: SaveReason.EXPLICIT }); } - - return Promise.resolve(true); } } @@ -297,12 +300,10 @@ class OverwriteReadonlyAction extends Action { super('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite")); } - run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ overwriteReadonly: true, reason: SaveReason.EXPLICIT }); } - - return Promise.resolve(true); } } @@ -314,12 +315,10 @@ class SaveIgnoreModifiedSinceAction extends Action { super('workbench.files.action.saveIgnoreModifiedSince', nls.localize('overwrite', "Overwrite")); } - run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT }); } - - return Promise.resolve(true); } } @@ -331,10 +330,8 @@ class ConfigureSaveConflictAction extends Action { super('workbench.files.action.configureSaveConflict', nls.localize('configure', "Configure")); } - run(): Promise { + async run(): Promise { this.preferencesService.openSettings(undefined, 'files.saveConflictResolution'); - - return Promise.resolve(true); } } @@ -342,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; @@ -371,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 2850d3ea76b..c2f3364c260 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -18,7 +18,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -28,12 +28,16 @@ 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 } 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'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -60,7 +64,6 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } private registerViews(): void { - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER); let viewDescriptorsToRegister: IViewDescriptor[] = []; @@ -187,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; @@ -254,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 6876d783a34..3b733762087 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -5,14 +5,14 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, ShowOpenedFileInNewWindow } from 'vs/workbench/contrib/files/browser/fileActions'; +import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, ShowOpenedFileInNewWindow, ReopenResourcesAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; @@ -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'; @@ -35,6 +36,7 @@ const category = { value: nls.localize('filesCategory', "File"), original: 'File const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.create(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.create(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), 'File: Compare Active File With...', category.value); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ReopenResourcesAction, ReopenResourcesAction.ID, ReopenResourcesAction.LABEL), 'File: Reopen With...', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusFilesExplorer, FocusFilesExplorer.ID, FocusFilesExplorer.LABEL), 'File: Focus on Files Explorer', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowActiveFileInExplorer, ShowActiveFileInExplorer.ID, ShowActiveFileInExplorer.LABEL), 'File: Reveal Active File in Side Bar', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.create(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL), 'File: Collapse Folders in Explorer', category.value); @@ -170,7 +172,7 @@ appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, Re appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFileSystemResource); -export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { +export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpression | undefined, group?: string): void { // Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { @@ -200,7 +202,7 @@ function appendSaveConflictEditorTitleAction(id: string, title: string, icon: Th // Menu registration - command palette -export function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpr): void { +export function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpression): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id, diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 43503864a71..146ebf56b1e 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'; @@ -17,9 +16,9 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { VIEWLET_ID, IExplorerService, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; -import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor, IEditorInput } 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, IQuickPickItem } 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'; @@ -35,7 +34,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Schemas } from 'vs/base/common/network'; import { IDialogService, IConfirmationResult, getFileNamesMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; import { Constants } from 'vs/base/common/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { coalesce } from 'vs/base/common/arrays'; @@ -45,7 +44,12 @@ 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'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -99,7 +103,7 @@ export class NewFileAction extends Action { })); } - run(): Promise { + run(): Promise { return this.commandService.executeCommand(NEW_FILE_COMMAND_ID); } } @@ -121,7 +125,7 @@ export class NewFolderAction extends Action { })); } - run(): Promise { + run(): Promise { return this.commandService.executeCommand(NEW_FOLDER_COMMAND_ID); } } @@ -139,12 +143,12 @@ export class GlobalNewUntitledFileAction extends Action { super(id, label); } - run(): Promise { - return this.editorService.openEditor({ options: { pinned: true } }); // untitled are always pinned + async run(): Promise { + await this.editorService.openEditor({ options: { pinned: true } }); // untitled are always pinned } } -async function deleteFiles(workingCopyService: IWorkingCopyService, textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); @@ -152,20 +156,24 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, textFileServ primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete"); } - const distinctElements = resources.distinctParents(elements, e => e.resource); - // Handle dirty + const distinctElements = resources.distinctParents(elements, e => e.resource); + const dirtyWorkingCopies = new Set(); + for (const distinctElement of distinctElements) { + for (const dirtyWorkingCopy of workingCopyFileService.getDirty(distinctElement.resource)) { + dirtyWorkingCopies.add(dirtyWorkingCopy); + } + } let confirmed = true; - const dirtyWorkingCopies = workingCopyService.dirtyWorkingCopies.filter(workingCopy => distinctElements.some(e => resources.isEqualOrParent(workingCopy.resource, e.resource))); - if (dirtyWorkingCopies.length) { + if (dirtyWorkingCopies.size) { let message: string; if (distinctElements.length > 1) { message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?"); } else if (distinctElements[0].isDirectory) { - if (dirtyWorkingCopies.length === 1) { + if (dirtyWorkingCopies.size === 1) { message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder {0} with unsaved changes in 1 file. Do you want to continue?", distinctElements[0].name); } else { - message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.length); + message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.size); } } else { message = nls.localize('dirtyMessageFileDelete', "You are deleting {0} with unsaved changes. Do you want to continue?", distinctElements[0].name); @@ -182,7 +190,6 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, textFileServ confirmed = false; } else { skipConfirm = true; - await Promise.all(dirtyWorkingCopies.map(dirty => dirty.revert())); } } @@ -244,7 +251,7 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, textFileServ // Call function try { - await Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true }))); + await Promise.all(distinctElements.map(e => workingCopyFileService.delete(e.resource, { useTrash: useTrash, recursive: true }))); } catch (error) { // Handle error to delete file(s) from a modal confirmation dialog @@ -274,7 +281,7 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, textFileServ skipConfirm = true; - return deleteFiles(workingCopyService, textFileService, dialogService, configurationService, elements, useTrash, skipConfirm); + return deleteFiles(workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm); } } } @@ -432,7 +439,7 @@ export function incrementFileName(name: string, isFolder: boolean, incrementalNa // folder.1=>folder.2 if (isFolder && name.match(/(\d+)$/)) { - return name.replace(/(\d+)$/, (match: string, ...groups: any[]) => { + return name.replace(/(\d+)$/, (match, ...groups) => { let number = parseInt(groups[0]); return number < maxNumber ? strings.pad(number + 1, groups[0].length) @@ -442,7 +449,7 @@ export function incrementFileName(name: string, isFolder: boolean, incrementalNa // 1.folder=>2.folder if (isFolder && name.match(/^(\d+)/)) { - return name.replace(/^(\d+)(.*)$/, (match: string, ...groups: any[]) => { + return name.replace(/^(\d+)(.*)$/, (match, ...groups) => { let number = parseInt(groups[0]); return number < maxNumber ? strings.pad(number + 1, groups[0].length) + groups[1] @@ -463,47 +470,136 @@ 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, ) { super(id, label); } - async run(): Promise { + async run(): Promise { const activeInput = this.editorService.activeEditor; - const activeResource = activeInput ? activeInput.getResource() : undefined; + const activeResource = activeInput ? activeInput.resource : undefined; if (activeResource) { // Compare with next editor that opens - const toDispose = this.editorService.overrideOpenEditor(editor => { + const toDispose = this.editorService.overrideOpenEditor({ + getEditorOverrides: (editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => { + return []; + }, + open: editor => { + // Only once! + toDispose.dispose(); - // Only once! - toDispose.dispose(); + // Open editor as diff + const resource = editor.resource; + if (resource) { + return { + override: this.editorService.openEditor({ + leftResource: activeResource, + rightResource: resource + }) + }; + } - // Open editor as diff - const resource = editor.getResource(); - if (resource) { - return { - override: this.editorService.openEditor({ - leftResource: activeResource, - rightResource: resource - }) - }; + return undefined; } - - 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.")); } } } +const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); +export class ReopenResourcesAction extends Action { + + static readonly ID = 'workbench.files.action.reopenWithEditor'; + static readonly LABEL = nls.localize('workbench.files.action.reopenWithEditor', "Reopen With..."); + + constructor( + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IEditorService private readonly editorService: IEditorService, + ) { + super(id, label); + } + + async run(): Promise { + const activeInput = this.editorService.activeEditor; + const activeEditorPane = this.editorService.activeEditorPane; + if (!activeEditorPane) { + return; + } + + const options = activeEditorPane.options; + const group = activeEditorPane.group; + const activeResource = activeInput ? activeInput.resource : undefined; + + if (!activeResource) { + return; + } + + const overrides = this.editorService.getEditorOverrides(activeInput!, options, group); + const items: (IQuickPickItem & { handler?: IOpenEditorOverrideHandler })[] = overrides.map((override) => { + return { + handler: override[0], + id: override[1].id, + label: override[1].label, + description: override[1].active ? 'Currently Active' : undefined, + detail: override[1].detail + }; + }); + + if (!items.length) { + return; + } + + items.unshift({ + id: 'default', + label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), + description: activeInput instanceof FileEditorInput ? 'Currently Active' : undefined, + detail: builtinProviderDisplayName + }); + + const picker = this.quickInputService.createQuickPick<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler })>(); + picker.items = items; + picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", resources.basename(activeResource)); + + const pickedItem = await new Promise<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler }) | undefined>(resolve => { + picker.onDidAccept(() => { + resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); + picker.dispose(); + }); + + picker.show(); + }); + + if (!pickedItem) { + return; + } + + if (pickedItem.id === 'default') { + const fileEditorInput = this.editorService.createEditorInput({ resource: activeResource!, forceFile: true }); + const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true }; + + await this.editorService.openEditor(fileEditorInput, textOptions, group); + return; + } + + await pickedItem.handler!.open(activeInput!, options, group, pickedItem.id); + } +} + export class ToggleAutoSaveAction extends Action { static readonly ID = 'workbench.action.toggleAutoSave'; static readonly LABEL = nls.localize('toggleAutoSave', "Toggle Auto Save"); @@ -516,7 +612,7 @@ export class ToggleAutoSaveAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { return this.filesConfigurationService.toggleAutoSave(); } } @@ -539,7 +635,7 @@ export abstract class BaseSaveAllAction extends Action { this.registerListeners(); } - protected abstract doRun(context: any): Promise; + protected abstract doRun(context: unknown): Promise; private registerListeners(): void { @@ -555,7 +651,7 @@ export abstract class BaseSaveAllAction extends Action { } } - async run(context?: any): Promise { + async run(context?: unknown): Promise { try { await this.doRun(context); } catch (error) { @@ -573,7 +669,7 @@ export class SaveAllAction extends BaseSaveAllAction { return 'explorer-action codicon-save-all'; } - protected doRun(context: any): Promise { + protected doRun(): Promise { return this.commandService.executeCommand(SAVE_ALL_COMMAND_ID); } } @@ -587,7 +683,7 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { return 'explorer-action codicon-save-all'; } - protected doRun(context: any): Promise { + protected doRun(context: unknown): Promise { return this.commandService.executeCommand(SAVE_ALL_IN_GROUP_COMMAND_ID, {}, context); } } @@ -601,7 +697,7 @@ export class CloseGroupAction extends Action { super(id, label, 'codicon-close-all'); } - run(context?: any): Promise { + run(context?: unknown): Promise { return this.commandService.executeCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, {}, context); } } @@ -619,8 +715,8 @@ export class FocusFilesExplorer extends Action { super(id, label); } - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true); + async run(): Promise { + await this.viewletService.openViewlet(VIEWLET_ID, true); } } @@ -639,15 +735,13 @@ export class ShowActiveFileInExplorer extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); } else { this.notificationService.info(nls.localize('openFileToShow', "Open a file first to show it in the explorer")); } - - return true; } } @@ -668,7 +762,7 @@ export class CollapseExplorerView extends Action { })); } - async run(): Promise { + async run(): Promise { const explorerViewlet = (await this.viewletService.openViewlet(VIEWLET_ID))?.getViewPaneContainer() as ExplorerViewPaneContainer; const explorerView = explorerViewlet.getExplorerView(); if (explorerView) { @@ -695,7 +789,7 @@ export class RefreshExplorerView extends Action { })); } - async run(): Promise { + async run(): Promise { await this.viewletService.openViewlet(VIEWLET_ID); this.explorerService.refresh(); } @@ -717,7 +811,7 @@ export class ShowOpenedFileInNewWindow extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { @@ -728,23 +822,27 @@ export class ShowOpenedFileInNewWindow extends Action { } else { this.notificationService.info(nls.localize('openFileToShowInNewWindow.nofile', "Open a file first to open in new window")); } - - return true; } } -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 +852,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 +894,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, '\\'); @@ -796,9 +907,8 @@ export class CompareWithClipboardAction extends Action { static readonly ID = 'workbench.files.action.compareWithClipboard'; static readonly LABEL = nls.localize('compareWithClipboard', "Compare Active File with Clipboard"); - private static readonly SCHEME = 'clipboardCompare'; - private registrationDisposal: IDisposable | undefined; + private static SCHEME_COUNTER = 0; constructor( id: string, @@ -813,24 +923,23 @@ export class CompareWithClipboardAction extends Action { this.enabled = true; } - async run(): Promise { + async run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); + const scheme = `clipboardCompare${CompareWithClipboardAction.SCHEME_COUNTER++}`; if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { const provider = this.instantiationService.createInstance(ClipboardContentProvider); - this.registrationDisposal = this.textModelService.registerTextModelContentProvider(CompareWithClipboardAction.SCHEME, provider); + this.registrationDisposal = this.textModelService.registerTextModelContentProvider(scheme, provider); } const name = resources.basename(resource); const editorLabel = nls.localize('clipboardComparisonLabel', "Clipboard ↔ {0}", name); - return this.editorService.openEditor({ leftResource: resource.with({ scheme: CompareWithClipboardAction.SCHEME }), rightResource: resource, label: editorLabel }).finally(() => { + await this.editorService.openEditor({ leftResource: resource.with({ scheme }), rightResource: resource, label: editorLabel }).finally(() => { dispose(this.registrationDisposal); this.registrationDisposal = undefined; }); } - - return true; } dispose(): void { @@ -849,13 +958,14 @@ class ClipboardContentProvider implements ITextModelContentProvider { ) { } async provideTextContent(resource: URI): Promise { - const model = this.modelService.createModel(await this.clipboardService.readText(), this.modeService.createByFilepathOrFirstLine(resource), resource); + const text = await this.clipboardService.readText(); + const model = this.modelService.createModel(text, this.modeService.createByFilepathOrFirstLine(resource), resource); return model; } } -function onErrorWithRetry(notificationService: INotificationService, error: any, retry: () => Promise): void { +function onErrorWithRetry(notificationService: INotificationService, error: unknown, retry: () => Promise): void { notificationService.prompt(Severity.Error, toErrorMessage(error, false), [{ label: nls.localize('retry', "Retry"), @@ -934,7 +1044,7 @@ CommandsRegistry.registerCommand({ export const renameHandler = (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); - const textFileService = accessor.get(ITextFileService); + const workingCopyFileService = accessor.get(IWorkingCopyFileService); const notificationService = accessor.get(INotificationService); const stats = explorerService.getContext(false); @@ -951,7 +1061,7 @@ export const renameHandler = (accessor: ServicesAccessor) => { const targetResource = resources.joinPath(parentResource, value); if (stat.resource.toString() !== targetResource.toString()) { try { - await textFileService.move(stat.resource, targetResource); + await workingCopyFileService.move(stat.resource, targetResource); refreshIfSeparator(value, explorerService); } catch (e) { notificationService.error(e); @@ -967,7 +1077,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); + await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); } }; @@ -976,7 +1086,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => { const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); + await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); } }; @@ -1002,7 +1112,7 @@ export const cutFileHandler = (accessor: ServicesAccessor) => { export const DOWNLOAD_COMMAND_ID = 'explorer.download'; const downloadFileHandler = (accessor: ServicesAccessor) => { const fileService = accessor.get(IFileService); - const textFileService = accessor.get(ITextFileService); + const workingCopyFileService = accessor.get(IWorkingCopyFileService); const fileDialogService = accessor.get(IFileDialogService); const explorerService = accessor.get(IExplorerService); const stats = explorerService.getContext(true); @@ -1037,7 +1147,7 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { defaultUri }); if (destination) { - await textFileService.copy(s.resource, destination, true); + await workingCopyFileService.copy(s.resource, destination, true); } else { // User canceled a download. In case there were multiple files selected we should cancel the remainder of the prompts #86100 canceled = true; @@ -1055,7 +1165,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { const clipboardService = accessor.get(IClipboardService); const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); - const textFileService = accessor.get(ITextFileService); + const workingCopyFileService = accessor.get(IWorkingCopyFileService); const notificationService = accessor.get(INotificationService); const editorService = accessor.get(IEditorService); const configurationService = accessor.get(IConfigurationService); @@ -1087,9 +1197,9 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { // Move/Copy File if (pasteShouldMove) { - return await textFileService.move(fileToPaste, targetFile); + return await workingCopyFileService.move(fileToPaste, targetFile); } else { - return await textFileService.copy(fileToPaste, targetFile); + return await workingCopyFileService.copy(fileToPaste, targetFile); } } catch (e) { onError(notificationService, new Error(nls.localize('fileDeleted', "The file to paste has been deleted or moved since you copied it. {0}", getErrorMessage(e)))); diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 210b8ef30a4..b02a9a4b480 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -280,7 +280,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: async (accessor) => { const editorService = accessor.get(IEditorService); const activeInput = editorService.activeEditor; - const resource = activeInput ? activeInput.getResource() : null; + const resource = activeInput ? activeInput.resource : null; const resources = resource ? [resource] : []; await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index ede1f68e4a6..4c816f7b2a1 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -13,9 +13,9 @@ 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 { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; +import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { BinaryFileEditor } from 'vs/workbench/contrib/files/browser/editors/binaryFileEditor'; @@ -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; @@ -127,24 +127,24 @@ class FileEditorInputFactory implements IEditorInputFactory { serialize(editorInput: EditorInput): string { const fileEditorInput = editorInput; - const resource = fileEditorInput.getResource(); - const fileInput: ISerializedFileInput = { + const resource = fileEditorInput.resource; + 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; }); } } @@ -154,8 +154,8 @@ Registry.as(EditorInputExtensions.EditorInputFactor // Register Explorer views Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExplorerViewletViewsContribution, LifecyclePhase.Starting); -// Register File Editor Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileEditorTracker, LifecyclePhase.Starting); +// Register Text File Editor Tracker +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TextFileEditorTracker, LifecyclePhase.Starting); // Register Text File Save Error Handler Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TextFileSaveErrorHandler, LifecyclePhase.Starting); @@ -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/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index ce694ddae30..04e0bf24825 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -48,7 +48,6 @@ flex: 0; /* do not steal space when label is hidden because we are in edit mode */ } - .explorer-viewlet .pane-header .count { min-width: fit-content; min-width: -moz-fit-content; @@ -56,28 +55,17 @@ align-items: center; } -.explorer-viewlet .pane-header .monaco-count-badge.hidden { +.pane-header .dirty-count.monaco-count-badge.hidden { display: none; } -.explorer-viewlet .monaco-count-badge { +.dirty-count.monaco-count-badge { padding: 1px 6px 2px; margin-left: 6px; min-height: auto; border-radius: 0; /* goes better when ellipsis shows up on narrow sidebar */ } -.explorer-viewlet .explorer-empty-view { - padding: 0 20px 0 20px; -} - -.explorer-viewlet .explorer-empty-view .monaco-button { - max-width: 260px; - margin-left: auto; - margin-right: auto; - display: block; -} - .explorer-viewlet .explorer-item.nonexistent-root { opacity: 0.5; } diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index c4f45d6d313..9cfc00be3ae 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -4,38 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as errors from 'vs/base/common/errors'; -import * as DOM from 'vs/base/browser/dom'; -import { Button } from 'vs/base/browser/ui/button/button'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { OpenFolderAction, AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; 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'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { Schemas } from 'vs/base/common/network'; -import { isWeb } from 'vs/base/common/platform'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class EmptyView extends ViewPane { static readonly ID: string = 'workbench.explorer.emptyView'; static readonly NAME = nls.localize('noWorkspace', "No Folder Opened"); - private button!: Button; - private messageElement!: HTMLElement; - constructor( options: IViewletViewOptions, @IThemeService themeService: IThemeService, @@ -45,64 +35,41 @@ export class EmptyView extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, @ILabelService private labelService: ILabelService, @IContextKeyService contextKeyService: IContextKeyService, - @IOpenerService openerService: IOpenerService + @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); - this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels())); - this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); + 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())); + } + + shouldShowWelcome(): boolean { + return true; } protected renderBody(container: HTMLElement): void { super.renderBody(container); - DOM.addClass(container, 'explorer-empty-view'); - container.tabIndex = 0; - - const messageContainer = document.createElement('div'); - DOM.addClass(messageContainer, 'section'); - container.appendChild(messageContainer); - - this.messageElement = document.createElement('p'); - messageContainer.appendChild(this.messageElement); - - this.button = new Button(messageContainer); - attachButtonStyler(this.button, this.themeService); - - this._register(this.button.onDidClick(() => { - if (!this.actionRunner) { - return; - } - const action = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE - ? this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL) - : this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL); - this.actionRunner.run(action).then(() => { - action.dispose(); - }, err => { - action.dispose(); - errors.onUnexpectedError(err); - }); - })); - 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, targetGroup => undefined); + dropHandler.handleDrop(e, () => undefined, () => undefined); }, - onDragEnter: (e) => { - const color = this.themeService.getTheme().getColor(listDropBackground); + onDragEnter: () => { + 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 => { @@ -112,36 +79,14 @@ export class EmptyView extends ViewPane { } })); - this.setLabels(); + this.refreshTitle(); } - private setLabels(): void { + private refreshTitle(): void { if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - this.messageElement.textContent = nls.localize('noWorkspaceHelp', "You have not yet added a folder to the workspace."); - if (this.button) { - this.button.label = nls.localize('addFolder', "Add Folder"); - } this.updateTitle(EmptyView.NAME); } else { - if (this.environmentService.configuration.remoteAuthority && !isWeb) { - const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.configuration.remoteAuthority); - this.messageElement.textContent = hostLabel ? nls.localize('remoteNoFolderHelp', "Connected to {0}", hostLabel) : nls.localize('connecting', "Connecting..."); - } else { - this.messageElement.textContent = nls.localize('noFolderHelp', "You have not yet opened a folder."); - } - if (this.button) { - this.button.label = nls.localize('openFolder', "Open Folder"); - } this.updateTitle(this.title); } } - - layoutBody(_size: number): void { - // no-op - } - - focus(): void { - this.button.element.focus(); - } - } 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 806bbb4fda1..f2e6ed1ee45 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -159,20 +159,20 @@ export class ExplorerView extends ViewPane { @IEditorService private readonly editorService: IEditorService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService keybindingService: IKeybindingService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, @IDecorationsService private readonly decorationService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, @IThemeService protected themeService: IWorkbenchThemeService, @IMenuService private readonly menuService: IMenuService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService telemetryService: ITelemetryService, @IExplorerService private readonly explorerService: IExplorerService, @IStorageService private readonly storageService: IStorageService, @IClipboardService private clipboardService: IClipboardService, @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); + 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); @@ -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,15 +668,23 @@ export class ExplorerView extends ViewPane { } if (item && item.parent) { - if (reveal) { - if (item.isDisposed) { - return this.onSelectResource(resource, reveal, retry + 1); - } - this.tree.reveal(item, 0.5); - } + try { + if (reveal) { + if (item.isDisposed) { + return this.onSelectResource(resource, reveal, retry + 1); + } - this.tree.setFocus([item]); - this.tree.setSelection([item]); + // 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 + } } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 79913360a0a..32f36adae90 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import * as DOM from 'vs/base/browser/dom'; import * as glob from 'vs/base/common/glob'; import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; @@ -37,7 +37,7 @@ import { Schemas } from 'vs/base/common/network'; import { DesktopDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { IDialogService, IConfirmation, getFileNamesMessage } from 'vs/platform/dialogs/common/dialogs'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { URI } from 'vs/base/common/uri'; @@ -55,7 +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 { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IEditorInput } from 'vs/workbench/common/editor'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -227,7 +227,7 @@ export interface IFileTemplateData { container: HTMLElement; } -export class FilesRenderer implements ICompressibleTreeRenderer, IAccessibilityProvider, IDisposable { +export class FilesRenderer implements ICompressibleTreeRenderer, IListAccessibilityProvider, IDisposable { static readonly ID = 'file'; private config: IFilesConfiguration; @@ -376,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 }; @@ -392,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; @@ -412,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()) { @@ -423,6 +438,9 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + showInputBoxNotification(); + }), DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { done(inputBox.isInputValid(), true); }), @@ -473,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 @@ -502,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) { @@ -515,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); } } @@ -641,10 +726,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { @IFileService private fileService: IFileService, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private instantiationService: IInstantiationService, - @ITextFileService private textFileService: ITextFileService, + @IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService, @IHostService private hostService: IHostService, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, - @IWorkingCopyService private workingCopyService: IWorkingCopyService + @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService ) { this.toDispose = []; @@ -945,15 +1029,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { const sourceFile = resource; const targetFile = joinPath(target.resource, basename(sourceFile)); - // if the target exists and is dirty, make sure to revert it. otherwise the dirty contents - // of the target file would replace the contents of the added file. since we already - // confirmed the overwrite before, this is OK. - if (this.workingCopyService.isDirty(targetFile)) { - await Promise.all(this.workingCopyService.getWorkingCopies(targetFile).map(workingCopy => workingCopy.revert({ soft: true }))); - } - - const copyTarget = joinPath(target.resource, basename(sourceFile)); - const stat = await this.textFileService.copy(sourceFile, copyTarget, true); + const stat = await this.workingCopyFileService.copy(sourceFile, targetFile, true); // if we only add one file, just open it directly if (resources.length === 1 && !stat.isDirectory) { this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); @@ -1040,7 +1116,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Reuse duplicate action if user copies if (isCopy) { const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; - const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(this.explorerService, target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); + const stat = await this.workingCopyFileService.copy(source.resource, findValidPasteFileTarget(this.explorerService, target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); if (!stat.isDirectory) { await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); } @@ -1056,7 +1132,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } try { - await this.textFileService.move(source.resource, targetResource); + await this.workingCopyFileService.move(source.resource, targetResource); } catch (error) { // Conflict if ((error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) { @@ -1065,7 +1141,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { const { confirmed } = await this.dialogService.confirm(confirm); if (confirmed) { try { - await this.textFileService.move(source.resource, targetResource, true /* overwrite */); + await this.workingCopyFileService.move(source.resource, targetResource, true /* overwrite */); } catch (error) { this.notificationService.error(error); } 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 7e53ad1f395..0b626785802 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,8 @@ 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'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; const $ = dom.$; @@ -76,18 +78,15 @@ export class OpenEditorsView extends ViewPane { @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService telemetryService: ITelemetryService, @IMenuService private readonly menuService: IMenuService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @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); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.structuralRefreshDelay = 0; this.listRefreshScheduler = new RunOnceScheduler(() => { @@ -185,7 +184,7 @@ export class OpenEditorsView extends ViewPane { super.renderHeaderTitle(container, this.title); const count = dom.append(container, $('.count')); - this.dirtyCountElement = dom.append(count, $('.monaco-count-badge')); + this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge')); this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; @@ -226,7 +225,8 @@ export class OpenEditorsView extends ViewPane { dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService), overrideStyles: { listBackground: this.getBackgroundColor() - } + }, + accessibilityProvider: new OpenEditorsAccessibilityProvider() }); this._register(this.list); this._register(this.listLabels); @@ -247,7 +247,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(); @@ -380,7 +380,7 @@ export class OpenEditorsView extends ViewPane { const element = e.element; const actions: IAction[] = []; - const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? element.editor.getResource() : {} }, actions, this.contextMenuService); + const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? element.editor.resource : {} }, actions, this.contextMenuService); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, @@ -418,8 +418,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 { @@ -491,9 +491,9 @@ interface IEditorGroupTemplateData { class OpenEditorActionRunner extends ActionRunner { public editor: OpenEditor | undefined; - run(action: IAction, context?: any): Promise { + async run(action: IAction): Promise { if (!this.editor) { - return Promise.resolve(); + return; } return super.run(action, { groupId: this.editor.groupId, editorIndex: this.editor.editorIndex }); @@ -595,7 +595,7 @@ class OpenEditorRenderer implements IListRenderer { + getAriaLabel(element: OpenEditor | IEditorGroup): string | null { + if (element instanceof OpenEditor) { + return `${element.editor.getName()} ${element.editor.getDescription()}`; + } + + return element.ariaLabel; + } +} diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 684d56f0a60..e4ae372e9d9 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -5,18 +5,21 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput } from 'vs/workbench/common/editor'; +import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput, GroupIdentifier, IMoveResult, isTextEditorPane } from 'vs/workbench/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; -import { ITextFileService, ModelState, LoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IReference, dispose } from 'vs/base/common/lifecycle'; +import { IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FILE_EDITOR_INPUT_ID, TEXT_FILE_EDITOR_ID, BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { isEqual } from 'vs/base/common/resources'; +import { Event } from 'vs/base/common/event'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; const enum ForceOpenAs { None, @@ -34,8 +37,11 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi private forceOpenAs: ForceOpenAs = ForceOpenAs.None; + private model: ITextFileEditorModel | undefined = undefined; private cachedTextFileModelReference: IReference | undefined = undefined; + private modelListeners: DisposableStore = this._register(new DisposableStore()); + constructor( resource: URI, preferredEncoding: string | undefined, @@ -51,6 +57,8 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi ) { super(resource, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService); + this.model = this.textFileService.files.get(resource); + if (preferredEncoding) { this.setPreferredEncoding(preferredEncoding); } @@ -58,12 +66,55 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi if (preferredMode) { this.setPreferredMode(preferredMode); } + + // If a file model already exists, make sure to wire it in + if (this.model) { + this.registerModelListeners(this.model); + } + } + + protected registerListeners(): void { + super.registerListeners(); + + // Attach to model that matches our resource once created + this._register(this.textFileService.files.onDidCreate(model => this.onDidCreateTextFileModel(model))); + } + + private onDidCreateTextFileModel(model: ITextFileEditorModel): void { + + // Once the text file model is created, we keep it inside + // the input to be able to implement some methods properly + if (isEqual(model.resource, this.resource)) { + this.model = model; + + this.registerModelListeners(model); + } + } + + private registerModelListeners(model: ITextFileEditorModel): void { + + // Clear any old + this.modelListeners.clear(); + + // re-emit some events from the model + this.modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this.modelListeners.add(model.onDidChangeOrphaned(() => this._onDidChangeLabel.fire())); + + // important: treat save errors as potential dirty change because + // a file that is in save conflict or error will report dirty even + // if auto save is turned on. + this.modelListeners.add(model.onDidSaveError(() => this._onDidChangeDirty.fire())); + + // remove model association once it gets disposed + this.modelListeners.add(Event.once(model.onDispose)(() => { + this.modelListeners.clear(); + this.model = undefined; + })); } getEncoding(): string | undefined { - const textModel = this.textFileService.files.get(this.resource); - if (textModel) { - return textModel.getEncoding(); + if (this.model) { + return this.model.getEncoding(); } return this.preferredEncoding; @@ -76,15 +127,14 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi setEncoding(encoding: string, mode: EncodingMode): void { this.setPreferredEncoding(encoding); - const textModel = this.textFileService.files.get(this.resource); - if (textModel) { - textModel.setEncoding(encoding, mode); - } + this.model?.setEncoding(encoding, mode); } setPreferredEncoding(encoding: string): void { this.preferredEncoding = encoding; - this.setForceOpenAsText(); // encoding is a good hint to open the file as text + + // encoding is a good hint to open the file as text + this.setForceOpenAsText(); } getPreferredMode(): string | undefined { @@ -94,15 +144,14 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi setMode(mode: string): void { this.setPreferredMode(mode); - const textModel = this.textFileService.files.get(this.resource); - if (textModel) { - textModel.setMode(mode); - } + this.model?.setMode(mode); } setPreferredMode(mode: string): void { this.preferredMode = mode; - this.setForceOpenAsText(); // mode is a good hint to open the file as text + + // mode is a good hint to open the file as text + this.setForceOpenAsText(); } setForceOpenAsText(): void { @@ -126,13 +175,18 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi } private decorateLabel(label: string): string { - const model = this.textFileService.files.get(this.resource); + const orphaned = this.model?.hasState(TextFileEditorModelState.ORPHAN); + const readonly = this.isReadonly(); - if (model?.hasState(ModelState.ORPHAN)) { + if (orphaned && readonly) { + return localize('orphanedReadonlyFile', "{0} (deleted, read-only)", label); + } + + if (orphaned) { return localize('orphanedFile', "{0} (deleted)", label); } - if (this.isReadonly()) { + if (readonly) { return localize('readonlyFile', "{0} (read-only)", label); } @@ -140,21 +194,19 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi } isDirty(): boolean { - const model = this.textFileService.files.get(this.resource); - if (!model) { - return false; + return !!(this.model?.isDirty()); + } + + isReadonly(): boolean { + if (this.model) { + return this.model.isReadonly(); } - return model.isDirty(); + return super.isReadonly(); } isSaving(): boolean { - const model = this.textFileService.files.get(this.resource); - if (!model) { - return false; - } - - if (model.hasState(ModelState.SAVED) || model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) { + if (this.model?.hasState(TextFileEditorModelState.SAVED) || this.model?.hasState(TextFileEditorModelState.CONFLICT) || this.model?.hasState(TextFileEditorModelState.ERROR)) { return false; // require the model to be dirty and not in conflict or error state } @@ -190,7 +242,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi encoding: this.preferredEncoding, reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model allowBinary: this.forceOpenAs === ForceOpenAs.Text, - reason: LoadReason.EDITOR + reason: TextFileLoadReason.EDITOR }); // This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary @@ -198,7 +250,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi // resolve() ensures we are not creating model references for these kind of resources. // In addition we have a bit of payload to take into account (encoding, reload) that the text resolver does not handle yet. if (!this.cachedTextFileModelReference) { - this.cachedTextFileModelReference = await this.createTextModelReference(); + this.cachedTextFileModelReference = await this.textModelResolverService.createModelReference(this.resource) as IReference; } return this.cachedTextFileModelReference.object; @@ -217,38 +269,36 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi } } - private async createTextModelReference(): Promise> { - const reference = await this.textModelResolverService.createModelReference(this.resource) as IReference; - - // Fire an initial dirty change if the model is already dirty - const model = reference.object; - if (model.isDirty()) { - this._onDidChangeDirty.fire(); - } - - this.registerModelListeners(model); - - return reference; - } - - private registerModelListeners(model: ITextFileEditorModel): void { - - // re-emit some events from the model - this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - this._register(model.onDidChangeOrphaned(() => this._onDidChangeLabel.fire())); - - // important: treat save errors as potential dirty change because - // a file that is in save conflict or error will report dirty even - // if auto save is turned on. - this._register(model.onDidSaveError(() => this._onDidChangeDirty.fire())); - } - private async doResolveAsBinary(): Promise { return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load(); } isResolved(): boolean { - return !!this.textFileService.files.get(this.resource); + return !!this.model; + } + + move(group: GroupIdentifier, target: URI): IMoveResult { + return { + editor: { + resource: target, + encoding: this.getEncoding(), + options: { + viewState: this.getViewStateFor(group) + } + } + }; + } + + private getViewStateFor(group: GroupIdentifier): IEditorViewState | undefined { + for (const editorPane of this.editorService.visibleEditorPanes) { + if (editorPane.group.id === group && isEqual(editorPane.input.resource, this.resource)) { + if (isTextEditorPane(editorPane)) { + return editorPane.getViewState(); + } + } + } + + return undefined; } matches(otherInput: unknown): boolean { @@ -265,6 +315,9 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi dispose(): void { + // Model + this.model = undefined; + // Model reference dispose(this.cachedTextFileModelReference); this.cachedTextFileModelReference = undefined; diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 90d81ae2e9d..dfaeb0945de 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -80,6 +80,7 @@ export class ExplorerItem { protected _isDirectoryResolved: boolean; private _isDisposed: boolean; public isError = false; + private _isExcluded = false; constructor( public resource: URI, @@ -95,6 +96,21 @@ export class ExplorerItem { this._isDisposed = false; } + get isExcluded(): boolean { + if (this._isExcluded) { + return true; + } + if (!this._parent) { + return false; + } + + return this._parent.isExcluded; + } + + set isExcluded(value: boolean) { + this._isExcluded = value; + } + get isDisposed(): boolean { return this._isDisposed; } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 077aa3ddc26..ce7e69f8a1b 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -56,8 +56,8 @@ export class ExplorerService implements IExplorerService { this.model = new ExplorerModel(this.contextService, this.fileService); this.disposables.add(this.model); - this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); - this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); + this.disposables.add(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e))); + this.disposables.add(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(e => { let affected = false; @@ -203,7 +203,7 @@ export class ExplorerService implements IExplorerService { refresh(): void { this.model.roots.forEach(r => r.forgetChildren()); this._onDidChangeItem.fire({ recursive: true }); - const resource = this.editorService.activeEditor ? this.editorService.activeEditor.getResource() : undefined; + const resource = this.editorService.activeEditor ? this.editorService.activeEditor.resource : undefined; const autoReveal = this.configurationService.getValue().explorer.autoReveal; if (resource && autoReveal) { @@ -214,7 +214,7 @@ export class ExplorerService implements IExplorerService { // File events - private onFileOperation(e: FileOperationEvent): void { + private onDidRunOperation(e: FileOperationEvent): void { // Add if (e.isOperation(FileOperation.CREATE) || e.isOperation(FileOperation.COPY)) { const addedElement = e.target; @@ -294,7 +294,7 @@ export class ExplorerService implements IExplorerService { } } - private onFileChanges(e: FileChangesEvent): void { + private onDidFilesChange(e: FileChangesEvent): void { // Check if an explorer refresh is necessary (delayed to give internal events a chance to react first) // Note: there is no guarantee when the internal events are fired vs real ones. Code has to deal with the fact that one might // be fired first over the other or not at all. diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 6f74715b376..963194be6e2 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -171,7 +171,7 @@ export class TextFileContentProvider extends Disposable implements ITextModelCon // Make sure to keep contents up to date when it changes if (!this.fileWatcherDisposable.value) { - this.fileWatcherDisposable.value = this.fileService.onFileChanges(changes => { + this.fileWatcherDisposable.value = this.fileService.onDidFilesChange(changes => { if (changes.contains(savedFileResource, FileChangeType.UPDATED)) { this.resolveEditorModel(resource, false /* do not create if missing */); // update model when resource changes } diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts index 369e59ea29f..52b1cf0897f 100644 --- a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts @@ -48,7 +48,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const activeInput = editorService.activeEditor; - const resource = activeInput ? activeInput.getResource() : null; + const resource = activeInput ? activeInput.resource : null; const resources = resource ? [resource] : []; revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); } diff --git a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts index 13d6cff9e2e..737eeea2d5c 100644 --- a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts @@ -3,7 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as os from 'os'; +import * as fs from 'fs'; import * as nls from 'vs/nls'; +import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorInput } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -11,9 +14,6 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { NativeTextFileEditor } from 'vs/workbench/contrib/files/electron-browser/textFileEditor'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import * as os from 'os'; -import * as fs from 'fs'; -import * as path from 'path'; // Register file editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -29,5 +29,5 @@ Registry.as(EditorExtensions.Editors).registerEditor( // Register mkdtemp command CommandsRegistry.registerCommand('mkdtemp', function () { - return fs.promises.mkdtemp(path.join(os.tmpdir(), 'vscodetmp-')); + return fs.promises.mkdtemp(join(os.tmpdir(), 'vscodetmp-')); }); diff --git a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts index 0390ba2cceb..4ea06a47056 100644 --- a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts @@ -24,6 +24,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; /** * An implementation of editor for file system resources. @@ -37,16 +38,17 @@ export class NativeTextFileEditor extends TextFileEditor { @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService textFileService: ITextFileService, @IElectronService private readonly electronService: IElectronService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IExplorerService explorerService: IExplorerService + @IExplorerService explorerService: IExplorerService, + @IConfigurationService configurationService: IConfigurationService ) { - super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, explorerService); + super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, textResourceConfigurationService, editorService, themeService, editorGroupService, textFileService, explorerService, configurationService); } protected handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 93617ce7314..6d7d1cdf745 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -7,9 +7,8 @@ import * as assert from 'assert'; import { Event } from 'vs/base/common/event'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TestFileService, TestFilesConfigurationService, TestEnvironmentService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { ITextFileService, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService } from 'vs/platform/files/common/files'; +import { TestFilesConfigurationService, workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; 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'; @@ -28,17 +27,6 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -class ServiceAccessor { - constructor( - @IEditorService public editorService: IEditorService, - @IEditorGroupsService public editorGroupService: IEditorGroupsService, - @ITextFileService public textFileService: ITextFileService, - @IFileService public fileService: TestFileService, - @IConfigurationService public configurationService: TestConfigurationService - ) { - } -} - suite('EditorAutoSave', () => { let disposables: IDisposable[] = []; @@ -59,17 +47,16 @@ suite('EditorAutoSave', () => { disposables = []; }); - test('editor auto saves after short delay if configured', async function () { + async function createEditorAutoSave(autoSaveConfig: object): Promise<[TestServiceAccessor, EditorPart, EditorAutoSave]> { const instantiationService = workbenchInstantiationService(); const configurationService = new TestConfigurationService(); - configurationService.setUserConfiguration('files', { autoSave: 'afterDelay', autoSaveDelay: 1 }); + configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), - configurationService, - TestEnvironmentService + configurationService )); const part = instantiationService.createInstance(EditorPart); @@ -81,14 +68,19 @@ suite('EditorAutoSave', () => { const editorService: EditorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - const accessor = instantiationService.createInstance(ServiceAccessor); + const accessor = instantiationService.createInstance(TestServiceAccessor); const editorAutoSave = instantiationService.createInstance(EditorAutoSave); + return [accessor, part, editorAutoSave]; + } + + test('editor auto saves after short delay if configured', async function () { + const [accessor, part, editorAutoSave] = await createEditorAutoSave({ autoSave: 'afterDelay', autoSaveDelay: 1 }); + const resource = toResource.call(this, '/path/index.txt'); const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; - model.textEditorModel.setValue('Super Good'); assert.ok(model.isDirty()); @@ -102,6 +94,28 @@ suite('EditorAutoSave', () => { (accessor.textFileService.files).dispose(); }); + test('editor auto saves on focus change if configured', async function () { + const [accessor, part, editorAutoSave] = await createEditorAutoSave({ autoSave: 'onFocusChange' }); + + const resource = toResource.call(this, '/path/index.txt'); + await accessor.editorService.openEditor({ resource, forceFile: true }); + + const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; + model.textEditorModel.setValue('Super Good'); + + assert.ok(model.isDirty()); + + await accessor.editorService.openEditor({ resource: toResource.call(this, '/path/index_other.txt') }); + + await awaitModelSaved(model); + + assert.ok(!model.isDirty()); + + part.dispose(); + editorAutoSave.dispose(); + (accessor.textFileService.files).dispose(); + }); + function awaitModelSaved(model: ITextFileEditorModel): Promise { return new Promise(c => { Event.once(model.onDidChangeDirty)(c); 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 3d987628e4f..5b78f85f045 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -7,34 +7,24 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { toResource } from 'vs/base/test/common/utils'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode, Verbosity } from 'vs/workbench/common/editor'; -import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; - -class ServiceAccessor { - constructor( - @IEditorService public editorService: IEditorService, - @ITextFileService public textFileService: TestTextFileService, - @IModelService public modelService: IModelService - ) { - } -} +import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; suite('Files - FileEditorInput', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('Basics', async function () { @@ -52,8 +42,8 @@ suite('Files - FileEditorInput', () => { assert.strictEqual('file.js', input.getName()); - assert.strictEqual(toResource.call(this, '/foo/bar/file.js').fsPath, input.getResource().fsPath); - assert(input.getResource() instanceof URI); + assert.strictEqual(toResource.call(this, '/foo/bar/file.js').fsPath, input.resource.fsPath); + assert(input.resource instanceof URI); input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar.html'), undefined, undefined); @@ -159,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(); @@ -187,4 +177,39 @@ suite('Files - FileEditorInput', () => { assert.ok(resolved); resolved.dispose(); }); + + test('attaches to model when created and reports dirty', async function () { + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); + + let listenerCount = 0; + const listener = input.onDidChangeDirty(() => { + listenerCount++; + }); + + // instead of going through file input resolve method + // we resolve the model directly through the service + const model = await accessor.textFileService.files.resolve(input.resource); + model.textEditorModel?.setValue('hello world'); + + assert.equal(listenerCount, 1); + assert.ok(input.isDirty()); + + input.dispose(); + listener.dispose(); + }); + + test('force open text/binary', async function () { + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); + input.setForceOpenAsBinary(); + + let resolved = await input.resolve(); + assert.ok(resolved instanceof BinaryEditorModel); + + input.setForceOpenAsText(); + + resolved = await input.resolve(); + assert.ok(resolved instanceof TextFileEditorModel); + + resolved.dispose(); + }); }); diff --git a/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts b/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts index 18735013da0..6b29c6e66d9 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts @@ -5,27 +5,19 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileContentProvider } from 'vs/workbench/contrib/files/common/files'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService } from 'vs/platform/files/common/files'; - -class ServiceAccessor { - constructor( - @IFileService public fileService: TestFileService - ) { - } -} suite('Files - FileOnDiskContentProvider', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('provideTextContent', async () => { diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts new file mode 100644 index 00000000000..9325e95d69b --- /dev/null +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.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 assert from 'assert'; +import { toResource } from 'vs/base/test/common/utils'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +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'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { Selection } from 'vs/editor/common/core/selection'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; + +suite('Files - TextFileEditor', () => { + + let disposables: IDisposable[] = []; + + setup(() => { + disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TextFileEditor, + TextFileEditor.ID, + 'Text File Editor' + ), + [new SyncDescriptor(FileEditorInput)] + )); + }); + + teardown(() => { + dispose(disposables); + disposables = []; + }); + + async function createPart(restoreViewState: boolean): Promise<[EditorPart, TestServiceAccessor, IInstantiationService, IEditorService]> { + const instantiationService = workbenchInstantiationService(); + + const configurationService = new TestConfigurationService(); + configurationService.setUserConfiguration('workbench', { editor: { restoreViewState } }); + instantiationService.stub(IConfigurationService, configurationService); + + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( + instantiationService.createInstance(MockContextKeyService), + configurationService + )); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const accessor = instantiationService.createInstance(TestServiceAccessor); + + await part.whenRestored; + + return [part, accessor, instantiationService, editorService]; + } + + test('text file editor preserves viewstate', async function () { + return viewStateTest(this, true); + }); + + test('text file editor resets viewstate if configured as such', async function () { + return viewStateTest(this, false); + }); + + async function viewStateTest(context: Mocha.ITestCallbackContext, restoreViewState: boolean): Promise { + const [part, accessor] = await createPart(restoreViewState); + + 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.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; + + if (restoreViewState) { + assert.ok(codeEditor.getSelection()?.equalsSelection(selection)); + } else { + assert.ok(!codeEditor.getSelection()?.equalsSelection(selection)); + } + + part.dispose(); + (accessor.textFileService.files).dispose(); + } +}); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts similarity index 70% rename from src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts rename to src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index 2719bd2b2b2..f50a26af1a9 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -5,12 +5,12 @@ import * as assert from 'assert'; import { Event } from 'vs/base/common/event'; -import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; +import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TestFileService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IResolvedTextFileEditorModel, snapshotToString, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { timeout } from 'vs/base/common/async'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; @@ -25,18 +25,10 @@ import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; -class ServiceAccessor { - constructor( - @IEditorService public editorService: IEditorService, - @IEditorGroupsService public editorGroupService: IEditorGroupsService, - @ITextFileService public textFileService: TestTextFileService, - @IFileService public fileService: TestFileService - ) { - } -} - -suite('Files - FileEditorTracker', () => { +suite('Files - TextFileEditorTracker', () => { let disposables: IDisposable[] = []; @@ -56,11 +48,29 @@ suite('Files - FileEditorTracker', () => { disposables = []; }); - test('file change event updates model', async function () { + async function createTracker(): Promise<[EditorPart, TestServiceAccessor, TextFileEditorTracker, IInstantiationService, IEditorService]> { const instantiationService = workbenchInstantiationService(); - const accessor = instantiationService.createInstance(ServiceAccessor); - const tracker = instantiationService.createInstance(FileEditorTracker); + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const accessor = instantiationService.createInstance(TestServiceAccessor); + + await part.whenRestored; + + const tracker = instantiationService.createInstance(TextFileEditorTracker); + + return [part, accessor, tracker, instantiationService, editorService]; + } + + test('file change event updates model', async function () { + const [, accessor, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -82,40 +92,19 @@ suite('Files - FileEditorTracker', () => { (accessor.textFileService.files).dispose(); }); - async function createTracker(): Promise<[EditorPart, ServiceAccessor, FileEditorTracker, IInstantiationService, IEditorService]> { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - instantiationService.stub(IEditorGroupsService, part); - - const editorService: EditorService = instantiationService.createInstance(EditorService); - instantiationService.stub(IEditorService, editorService); - - const accessor = instantiationService.createInstance(ServiceAccessor); - - await part.whenRestored; - - const tracker = instantiationService.createInstance(FileEditorTracker); - - return [part, accessor, tracker, instantiationService, editorService]; - } - test('dirty text file model opens as editor', async function () { const [part, accessor, tracker] = await createTracker(); 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(); @@ -125,7 +114,7 @@ suite('Files - FileEditorTracker', () => { 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)); @@ -145,4 +134,32 @@ suite('Files - FileEditorTracker', () => { Event.once(editorService.onDidActiveEditorChange)(c); }); } + + test('non-dirty files reload on window focus', async function () { + const [part, accessor, tracker] = await createTracker(); + + const resource = toResource.call(this, '/path/index.txt'); + + await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource, forceFile: true })); + + accessor.hostService.setFocus(false); + accessor.hostService.setFocus(true); + + await awaitModelLoadEvent(accessor.textFileService, resource); + + part.dispose(); + tracker.dispose(); + (accessor.textFileService.files).dispose(); + }); + + function awaitModelLoadEvent(textFileService: ITextFileService, resource: URI): Promise { + return new Promise(c => { + const listener = textFileService.files.onDidLoad(e => { + if (isEqual(e.model.resource, resource)) { + listener.dispose(); + c(); + } + }); + }); + } }); diff --git a/src/vs/workbench/contrib/issue/browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/browser/issue.contribution.ts index 4be89357d7b..65f8ffb8245 100644 --- a/src/vs/workbench/contrib/issue/browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/browser/issue.contribution.ts @@ -12,19 +12,23 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IWebIssueService, WebIssueService } from 'vs/workbench/contrib/issue/browser/issueService'; +import { OpenIssueReporterArgs, OpenIssueReporterActionId } from 'vs/workbench/contrib/issue/common/commands'; class RegisterIssueContribution implements IWorkbenchContribution { constructor(@IProductService readonly productService: IProductService) { if (productService.reportIssueUrl) { const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' }; - const OpenIssueReporterActionId = 'workbench.action.openIssueReporter'; const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); - CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string]) { + CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string] | OpenIssueReporterArgs) { let extensionId: string | undefined; - if (args && Array.isArray(args)) { - [extensionId] = args; + if (args) { + if (Array.isArray(args)) { + [extensionId] = args; + } else { + extensionId = args.extensionId; + } } return accessor.get(IWebIssueService).openReporter({ extensionId }); diff --git a/src/vs/workbench/contrib/issue/browser/issueService.ts b/src/vs/workbench/contrib/issue/browser/issueService.ts index daeb3fd7261..5fcf51485aa 100644 --- a/src/vs/workbench/contrib/issue/browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/browser/issueService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil'; +import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/browser/parts/editor/media/editorpicker.css b/src/vs/workbench/contrib/issue/common/commands.ts similarity index 62% rename from src/vs/workbench/browser/parts/editor/media/editorpicker.css rename to src/vs/workbench/contrib/issue/common/commands.ts index 49b6cd9921e..bcd6e252c88 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorpicker.css +++ b/src/vs/workbench/contrib/issue/common/commands.ts @@ -3,6 +3,10 @@ * 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 +export const OpenIssueReporterActionId = 'workbench.action.openIssueReporter'; + +export interface OpenIssueReporterArgs { + readonly extensionId?: string; + readonly issueTitle?: string; + readonly issueBody?: string; +} diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts index b9316bb1271..8f50836d67a 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts @@ -13,7 +13,8 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; import { WorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issueService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IIssueService } from 'vs/platform/issue/node/issue'; +import { IIssueService, IssueReporterData } from 'vs/platform/issue/node/issue'; +import { OpenIssueReporterArgs, OpenIssueReporterActionId } from 'vs/workbench/contrib/issue/common/commands'; const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' }; const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); @@ -21,16 +22,14 @@ const workbenchActionsRegistry = Registry.as(Extension if (!!product.reportIssueUrl) { workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory.value); - const OpenIssueReporterActionId = 'workbench.action.openIssueReporter'; const OpenIssueReporterActionLabel = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); - CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string]) { - let extensionId: string | undefined; - if (args && Array.isArray(args)) { - [extensionId] = args; - } + CommandsRegistry.registerCommand(OpenIssueReporterActionId, function (accessor, args?: [string] | OpenIssueReporterArgs) { + const data: Partial = Array.isArray(args) + ? { extensionId: args[0] } + : args || {}; - return accessor.get(IWorkbenchIssueService).openReporter({ extensionId }); + return accessor.get(IWorkbenchIssueService).openReporter(data); }); const command: ICommandAction = { 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 70b5d458802..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); } @@ -80,7 +80,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } const watcher = this.fileService.watch(dirname(file)); - const disposable = this.fileService.onFileChanges(e => { + const disposable = this.fileService.onDidFilesChange(e => { if (e.contains(file, FileChangeType.ADDED) || e.contains(file, FileChangeType.UPDATED)) { watcher.dispose(); disposable.dispose(); 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/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index ffd8168c256..1ca8c91921b 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -25,7 +25,7 @@ import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeNode import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { IMatch } from 'vs/base/common/filters'; import { Event, Emitter } from 'vs/base/common/event'; -import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Action, IAction } from 'vs/base/common/actions'; @@ -48,6 +48,10 @@ import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { IFileService } from 'vs/platform/files/common/files'; +import { domEvent } from 'vs/base/browser/event'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Progress } from 'vs/platform/progress/common/progress'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -67,7 +71,7 @@ interface IRelatedInformationTemplateData { description: HighlightedLabel; } -export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider { +export class MarkersTreeAccessibilityProvider implements IListAccessibilityProvider { constructor(@ILabelService private readonly labelService: ILabelService) { } @@ -374,14 +378,21 @@ class MarkerWidget extends Disposable { dom.append(parent, this._codeLink); this._codeLink.setAttribute('href', codeLink); + this._codeLink.tabIndex = 0; - this._codeLink.onclick = (e) => { - e.preventDefault(); - if ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey)) { - this._openerService.open(codeUri); - e.stopPropagation(); - } - }; + const onClick = Event.chain(domEvent(this._codeLink, 'click')) + .filter(e => ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey))) + .event; + const onEnterPress = Event.chain(domEvent(this._codeLink, 'keydown')) + .map(e => new StandardKeyboardEvent(e)) + .filter(e => e.keyCode === KeyCode.Enter) + .event; + const onOpen = Event.any(onClick, onEnterPress); + + this._register(onOpen(e => { + dom.EventHelper.stop(e, true); + this._openerService.open(codeUri); + })); const code = new HighlightedLabel(dom.append(this._codeLink, dom.$('.marker-code')), false); const codeMatches = filterData && filterData.codeMatches || []; @@ -632,7 +643,7 @@ export class MarkerViewModel extends Disposable { this.codeActionsPromise = createCancelablePromise(cancellationToken => { return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: CodeActionTriggerType.Manual, filter: { include: CodeActionKind.QuickFix } - }, cancellationToken).then(actions => { + }, Progress.None, cancellationToken).then(actions => { return this._register(actions); }); }); @@ -845,6 +856,7 @@ export class ResourceDragAndDrop implements ITreeDragAndDrop { registerThemingParticipant((theme, collector) => { const linkFg = theme.getColor(textLinkForeground); if (linkFg) { - collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link span:hover { color: ${linkFg}; }`); + collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: ${linkFg}; }`); + collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-list:focus .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: inherit; }`); } }); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index ef7389ec105..95e92b37c4e 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -13,17 +13,17 @@ 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 { Iterator } from 'vs/base/common/iterator'; +import { IContextKey, IContextKeyService, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Iterable } 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'; @@ -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,23 +44,19 @@ 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'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { - const markersIt = Iterator.fromArray(resourceMarkers.markers); - - return Iterator.map(markersIt, m => { - const relatedInformationIt = Iterator.from(m.relatedInformation); - const children = Iterator.map(relatedInformationIt, r => ({ element: r })); +function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterable> { + return Iterable.map(resourceMarkers.markers, m => { + const relatedInformationIt = Iterable.from(m.relatedInformation); + const children = Iterable.map(relatedInformationIt, r => ({ element: r })); return { element: m, children }; }); - } export class MarkersView extends ViewPane implements IMarkerFilterController { @@ -75,29 +71,35 @@ 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, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService telemetryService: ITelemetryService, @IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -108,8 +110,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); - 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 +121,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 +149,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 +157,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 +166,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 +196,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 +312,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 +325,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) { @@ -302,16 +335,16 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } else { resourceMarkers = this.markersWorkbenchService.markersModel.resourceMarkers; } - this.tree.setChildren(null, Iterator.map(Iterator.fromArray(resourceMarkers), m => ({ element: m, children: createResourceMarkersIterator(m) }))); + this.tree.setChildren(null, Iterable.map(resourceMarkers, m => ({ element: m, children: createResourceMarkersIterator(m) }))); } 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 +354,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 +371,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 { @@ -349,7 +382,6 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private createArialLabelElement(parent: HTMLElement): void { this.ariaLabelElement = dom.append(parent, dom.$('')); this.ariaLabelElement.setAttribute('id', 'markers-panel-arialabel'); - this.ariaLabelElement.setAttribute('aria-live', 'polite'); } private createTree(parent: HTMLElement): void { @@ -383,7 +415,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() } }, )); @@ -417,7 +449,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(); } })); @@ -425,7 +457,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(); } })); @@ -463,7 +495,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(); @@ -509,7 +541,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(); @@ -517,7 +549,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private setCurrentActiveEditor(): void { const activeEditor = this.editorService.activeEditor; - this.currentActiveResource = activeEditor ? withUndefinedAsNull(activeEditor.getResource()) : null; + this.currentActiveResource = activeEditor ? withUndefinedAsNull(activeEditor.resource) : null; } private onSelected(): void { @@ -553,7 +585,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) { @@ -612,16 +644,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'); @@ -750,16 +782,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(); @@ -791,11 +819,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" : { @@ -810,13 +838,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(); @@ -853,7 +881,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..451e8348f53 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts @@ -0,0 +1,1018 @@ +/*--------------------------------------------------------------------------------------------- + * 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 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 EDITOR_WIDGET_ACTION_WEIGHT = KeybindingWeight.EditorContrib; // smaller than Suggest Widget, etc + +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: EDITOR_WIDGET_ACTION_WEIGHT + }, + icon: { id: 'codicon/play' }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(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 Cell Execution"), + category: NOTEBOOK_ACTIONS_CATEGORY, + icon: { id: 'codicon/primitive-square' }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(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: EDITOR_WIDGET_ACTION_WEIGHT + } + }); + } + + 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: EDITOR_WIDGET_ACTION_WEIGHT + } + }); + } + + 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"), + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + 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"), + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + 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: 'workbench.action.quitNotebookEdit', + title: localize('notebookActions.quitEditing', "Quit Notebook Cell Editing"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), + primary: KeyCode.Escape, + weight: EDITOR_WIDGET_ACTION_WEIGHT - 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_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 + }, + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + 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 + }, + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + 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 isCellActionContext(context: any): context is INotebookCellActionContext { + return context && !!context.cell && !!context.notebookEditor; +} + +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 (!isCellActionContext(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 (!isCellActionContext(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 (!isCellActionContext(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) + }, + keybinding: { + primary: KeyCode.Delete, + mac: { + primary: KeyMod.CtrlCmd | KeyCode.Backspace + }, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + weight: KeybindingWeight.WorkbenchContrib + }, + icon: { id: 'codicon/trash' }, + f1: true + }); + } + + run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!isCellActionContext(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 (!isCellActionContext(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 (!isCellActionContext(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"), + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!isCellActionContext(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"), + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) { + if (!isCellActionContext(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: EDITOR_WIDGET_ACTION_WEIGHT + } + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(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: EDITOR_WIDGET_ACTION_WEIGHT + }, + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(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/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css new file mode 100644 index 00000000000..e9104363732 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -0,0 +1,410 @@ +/*--------------------------------------------------------------------------------------------- + * 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:focus-within, +.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/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts new file mode 100644 index 00000000000..56be384831a --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.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 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({ + getEditorOverrides: (editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => { + let resource = editor.resource; + if (!resource) { + return []; + } + + const infos = notebookService.getContributedNotebookProviders(resource); + + return infos.map(info => { + return { + label: info.displayName, + id: info.id, + active: editor instanceof NotebookEditorInput && editor.viewType === info.id, + detail: info.providerDisplayName + }; + }); + }, + open: (editor, options, group, id) => this.onEditorOpening(editor, options, group, id) + }); + + 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, id: string | undefined): IOpenEditorOverride | undefined { + let resource = originalInput.resource; + if (!resource) { + return undefined; + } + + let info: NotebookProviderInfo | undefined; + const data = CellUri.parse(resource); + if (data) { + const infos = this.notebookService.getContributedNotebookProviders(data.notebook); + + if (infos.length) { + const info = id === undefined ? infos[0] : (infos.find(info => info.id === id) || infos[0]); + // 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) }; + } + } + + const infos = this.notebookService.getContributedNotebookProviders(resource); + info = id === undefined ? infos[0] : infos.find(info => info.id === id); + + 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/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..352f6302bc1 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -0,0 +1,962 @@ +/*--------------------------------------------------------------------------------------------- + * 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/notebook'; +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 { 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, + }, + accessibilityProvider: { + getAriaLabel() { return null; } + } + }, + ); + + 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..ce26f0c93cf --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * 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) { + if (!await this.notebookService.canResolve(this.viewType!)) { + throw new Error(`Cannot open notebook of type '${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..492f3922f4e --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookService.ts @@ -0,0 +1,303 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { 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; + + 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 || [], + providerDisplayName: extension.description.isBuiltin ? nls.localize('builtinProviderDisplayName', "Built-in") : extension.description.displayName || extension.description.identifier.value, + })); + } + } + // 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)) { + // this awaits full activation of all matching extensions + await this.extensionService.activateByEvent(`onNotebookEditor:${viewType}`); + } + return this._notebookProviders.has(viewType); + } + + registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) { + this._notebookProviders.set(viewType, { extensionData, controller }); + } + + 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..7e9c4102ab4 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -0,0 +1,493 @@ +/*--------------------------------------------------------------------------------------------- + * 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) { + element.revealRangeInCenter(range); + } + }; + + 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..a2a79f6793b --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -0,0 +1,532 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// eslint-disable-next-line code-import-patterns +import 'vs/css!vs/workbench/contrib/notebook/browser/media/notebook'; +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 { 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'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; + +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 `)); + addCodeCell.tabIndex = 0; + const insertCellBelow = this.instantiationService.createInstance(InsertCodeCellAction); + + disposables.add(DOM.addDisposableListener(addCodeCell, DOM.EventType.CLICK, () => { + this.actionRunner.run(insertCellBelow, context); + })); + + disposables.add((DOM.addDisposableListener(addCodeCell, DOM.EventType.KEY_DOWN, async e => { + const event = new StandardKeyboardEvent(e); + if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { + e.preventDefault(); + e.stopPropagation(); + this.actionRunner.run(insertCellBelow, context); + } + }))); + + DOM.append(container, $('.seperator-short')); + const addMarkdownCell = DOM.append(container, $('span.button')); + addMarkdownCell.innerHTML = renderCodicons(escape('$(add) Markdown ')); + addMarkdownCell.tabIndex = 0; + const insertMarkdownBelow = this.instantiationService.createInstance(InsertMarkdownCellAction); + disposables.add(DOM.addDisposableListener(addMarkdownCell, DOM.EventType.CLICK, () => { + this.actionRunner.run(insertMarkdownBelow, context); + })); + + disposables.add((DOM.addDisposableListener(addMarkdownCell, DOM.EventType.KEY_DOWN, async e => { + const event = new StandardKeyboardEvent(e); + if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { + e.preventDefault(); + e.stopPropagation(); + 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 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); + + const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); + + 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..3de88c38c8b --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -0,0 +1,448 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; + +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 && this.viewCell.layoutInfo.totalHeight > totalHeight) { + 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'); + mimeTypePicker.tabIndex = 0; + mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype"); + outputItemDiv.appendChild(mimeTypePicker); + this.outputResizeListeners.get(currOutput)!.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { + if (e.leftButton) { + e.preventDefault(); + e.stopPropagation(); + await this.pickActiveMimeTypeRenderer(transformedDisplayOutput); + } + })); + + this.outputResizeListeners.get(currOutput)!.add((DOM.addDisposableListener(mimeTypePicker, DOM.EventType.KEY_DOWN, async e => { + const event = new StandardKeyboardEvent(e); + if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { + 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 nls.localize('builtinRenderInfo', "built-in"); + } + + let renderInfo = this.notebookService.getRendererInfo(renderId); + + if (renderInfo) { + return renderInfo.id.value; + } + + return nls.localize('builtinRenderInfo', "built-in"); + } + + 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..618e4a6fbd0 --- /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 : this._layoutInfo.editorWidth; + + this._layoutInfo = { + fontInfo: state.font || this._layoutInfo.fontInfo, + 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..b7623f3f888 --- /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 + 1}`, + 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..a1f4ca196a4 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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[]; + readonly providerDisplayName: string; + + constructor(descriptor: { + readonly id: string; + readonly displayName: string; + readonly selector: readonly NotebookSelector[]; + readonly providerDisplayName: string; + }) { + this.id = descriptor.id; + this.displayName = descriptor.displayName; + this.selector = descriptor.selector; + this.providerDisplayName = descriptor.providerDisplayName; + } + + 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/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 268c699b857..3dc1cf7fb70 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -12,13 +12,12 @@ import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymb import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -// import './outlineNavigation'; - export const PANEL_ID = 'panel.view.outline'; const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), + containerIcon: 'codicon-symbol-class', ctorDescriptor: new SyncDescriptor(OutlinePane), canToggleVisibility: true, canMoveView: true, diff --git a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts deleted file mode 100644 index bf145c0cf44..00000000000 --- a/src/vs/workbench/contrib/outline/browser/outlineNavigation.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 { Range } from 'vs/editor/common/core/range'; -import { IPosition, Position } from 'vs/editor/common/core/position'; -import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; -import { localize } from 'vs/nls'; -import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; -import { EditorAction, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { forEach } from 'vs/base/common/collections'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; -import { binarySearch } from 'vs/base/common/arrays'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; - -class FlatOutline { - - readonly elements: OutlineElement[] = []; - readonly _elementPositions: IPosition[]; - - constructor(model: OutlineModel, filter: OutlineFilter) { - - const walk = (element: TreeElement) => { - if (element instanceof OutlineElement && !filter.filter(element)) { - return; - } - if (element instanceof OutlineElement) { - this.elements.push(element); - } - forEach(element.children, entry => walk(entry.value)); - }; - - walk(model); - this.elements.sort(FlatOutline._compare); - this._elementPositions = this.elements.map(element => ({ - lineNumber: element.symbol.range.startLineNumber, - column: element.symbol.range.startColumn - })); - } - - private static _compare(a: TreeElement, b: TreeElement): number { - return (a instanceof OutlineElement && b instanceof OutlineElement) - ? Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) - : 0; - } - - find(position: IPosition, preferAfter: boolean): number { - const idx = binarySearch(this._elementPositions, position, Position.compare); - if (idx >= 0) { - return idx; - } else if (preferAfter) { - return ~idx; - } else { - return ~idx - 1; - } - } -} - -export class OutlineNavigation implements IEditorContribution { - - public static readonly ID = 'editor.contrib.OutlineNavigation'; - - public static get(editor: ICodeEditor): OutlineNavigation { - return editor.getContribution(OutlineNavigation.ID); - } - - private readonly _editor: ICodeEditor; - - private _cts?: CancellationTokenSource; - - constructor( - editor: ICodeEditor, - @ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService, - ) { - this._editor = editor; - } - - dispose(): void { - if (this._cts) { - this._cts.dispose(true); - } - } - - async goto(up: boolean) { - - if (this._cts) { - this._cts.dispose(true); - } - - if (!this._editor.hasModel()) { - return; - } - - const textModel = this._editor.getModel(); - const position = this._editor.getPosition(); - - this._cts = new EditorStateCancellationTokenSource(this._editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Scroll); - - const filter = new OutlineFilter('outline', this._textResourceConfigService); - const outlineModel = await OutlineModel.create(textModel, this._cts.token); - - if (this._cts.token.isCancellationRequested) { - return; - } - - const symbols = new FlatOutline(outlineModel, filter); - const idx = symbols.find(position, !up); - const element = symbols.elements[idx]; - - if (element) { - if (Range.containsPosition(element.symbol.selectionRange, position)) { - // at the "name" of a symbol -> move - const nextElement = symbols.elements[idx + (up ? -1 : +1)]; - this._revealElement(nextElement); - - } else { - // enclosing, lastBefore, or firstAfter element - this._revealElement(element); - } - } - } - - private _revealElement(element: OutlineElement | undefined): void { - if (!element) { - return; - } - const pos = Range.lift(element.symbol.selectionRange).getStartPosition(); - this._editor.setPosition(pos); - this._editor.revealPosition(pos, ScrollType.Smooth); - - const modelNow = this._editor.getModel(); - const ids = this._editor.deltaDecorations([], [{ - range: element.symbol.selectionRange, - options: { - className: 'symbolHighlight', - } - }]); - setTimeout(() => { - if (modelNow === this._editor.getModel()) { - this._editor.deltaDecorations(ids, []); - } - }, 350); - } -} - -registerEditorContribution(OutlineNavigation.ID, OutlineNavigation); - -registerEditorAction(class extends EditorAction { - - constructor() { - super({ - id: 'editor.action.gotoNextSymbol', - label: localize('label.next', "Go to Next Symbol"), - alias: 'Go to Next Symbol', - precondition: EditorContextKeys.hasDocumentSymbolProvider, - kbOpts: { - weight: KeybindingWeight.EditorContrib, - kbExpr: EditorContextKeys.focus, - primary: undefined, - mac: { - primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.DownArrow, - }, - } - }); - } - - run(_accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { - OutlineNavigation.get(editor).goto(false); - } -}); - -registerEditorAction(class extends EditorAction { - - constructor() { - super({ - id: 'editor.action.gotoPrevSymbol', - label: localize('label.prev', "Go to Previous Symbol"), - alias: 'Go to Previous Symbol', - precondition: EditorContextKeys.hasDocumentSymbolProvider, - kbOpts: { - weight: KeybindingWeight.EditorContrib, - kbExpr: EditorContextKeys.focus, - primary: undefined, - mac: { - primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.UpArrow, - }, - } - }); - } - - run(_accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { - OutlineNavigation.get(editor).goto(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 c4ba64d1e82..7c6ceea1755 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -40,7 +40,7 @@ import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { OutlineConfigKeys, OutlineViewFocused, OutlineViewFiltered } from 'vs/editor/contrib/documentSymbols/outline'; import { FuzzyScore } from 'vs/base/common/filters'; -import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineFilter, OutlineAccessibilityProvider } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { IDataTreeViewState } from 'vs/base/browser/ui/tree/dataTree'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { basename } from 'vs/base/common/resources'; @@ -50,6 +50,7 @@ import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; class RequestState { @@ -94,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()) { @@ -270,8 +271,9 @@ export class OutlinePane extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, _configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, _configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService); this._outlineViewState.restore(this._storageService); this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService); this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService); @@ -338,6 +340,7 @@ export class OutlinePane extends ViewPane { filter: this._treeFilter, identityProvider: new OutlineIdentityProvider(), keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), + accessibilityProvider: new OutlineAccessibilityProvider(), hideTwistiesOfChildlessElements: true, overrideStyles: { listBackground: this.getBackgroundColor() @@ -636,7 +639,7 @@ export class OutlinePane extends ViewPane { options: { preserveFocus: !focus, selection: Range.collapseToStart(element.symbol.selectionRange), - selectionRevealType: TextEditorSelectionRevealType.NearTop, + selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport, } }, this._editorService.getActiveCodeEditor(), diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index bd1b1846456..560b003c2d2 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -29,7 +29,7 @@ export class LogViewerInput extends ResourceEditorInput { static readonly ID = 'workbench.editorinputs.output'; constructor( - private readonly outputChannelDescriptor: IFileOutputChannelDescriptor, + outputChannelDescriptor: IFileOutputChannelDescriptor, @ITextModelService textModelResolverService: ITextModelService, @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, @@ -56,10 +56,6 @@ export class LogViewerInput extends ResourceEditorInput { getTypeId(): string { return LogViewerInput.ID; } - - getResource(): URI { - return this.outputChannelDescriptor.file; - } } export class LogViewer extends AbstractTextResourceEditor { 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..f80049ed1dc --- /dev/null +++ b/src/vs/workbench/contrib/output/browser/media/output.css @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +.monaco-workbench .part.sidebar > .title > .title-actions .switch-output, +.monaco-pane-view .pane > .pane-header .monaco-action-bar .switch-output { + border-width: 1px; + border-style: solid; +} + +.part.panel > .title > .title-actions .switch-output > .monaco-select-box { + border-width: 1px; + border-style: solid; +} + +.monaco-workbench .part.sidebar > .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.sidebar > .title > .title-actions .switch-output { + border-radius: 4px; +} + +.monaco-workbench .part.sidebar > .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.sidebar > .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/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index cefd280206f..ea753245ff4 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -112,7 +112,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo } const outputView = await this.viewsService.openView(OUTPUT_VIEW_ID, !preserveFocus); if (outputView && channel) { - outputView!.showChannel(channel, !!preserveFocus); + outputView.showChannel(channel, !!preserveFocus); } } diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 4f455e49071..bd56258508d 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,31 +25,44 @@ 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, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IInstantiationService instantiationService: IInstantiationService, @IOutputService private readonly outputService: IOutputService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + 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()); @@ -77,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(); } })); @@ -89,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; } })); } @@ -104,34 +120,20 @@ 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); - const channel = this.channelId ? this.outputService.getChannel(this.channelId) : undefined; - if (visible && channel) { + let channel: IOutputChannel | undefined = undefined; + if (visible) { + channel = this.channelId ? this.outputService.getChannel(this.channelId) : this.outputService.getActiveChannel(); + } + if (channel) { this.setInput(channel); } else { this.clearInput(); @@ -140,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); } @@ -251,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])); @@ -260,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.borderColor = colors.selectBorder ? `${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/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index fb206077648..c43d174e670 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -8,7 +8,8 @@ import { exists, readdir, readFile, rimraf } from 'vs/base/node/pfs'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import product from 'vs/platform/product/common/product'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -24,7 +25,7 @@ export class StartupProfiler implements IWorkbenchContribution { constructor( @IDialogService private readonly _dialogService: IDialogService, - @IEnvironmentService private readonly _environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, @ITextModelService private readonly _textModelResolverService: ITextModelService, @IClipboardService private readonly _clipboardService: IClipboardService, @ILifecycleService lifecycleService: ILifecycleService, diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index 789a38d7dbb..aec39827d26 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -8,7 +8,8 @@ import { timeout } from 'vs/base/common/async'; import { promisify } from 'util'; import { onUnexpectedError } from 'vs/base/common/errors'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; import product from 'vs/platform/product/common/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -33,7 +34,7 @@ export class StartupTimings implements IWorkbenchContribution { @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IUpdateService private readonly _updateService: IUpdateService, - @IEnvironmentService private readonly _envService: IEnvironmentService + @IWorkbenchEnvironmentService private readonly _envService: INativeWorkbenchEnvironmentService ) { // this._report().catch(onUnexpectedError); @@ -98,8 +99,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..a5bb6604061 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'; @@ -46,6 +46,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { Emitter, Event } from 'vs/base/common/event'; import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; const $ = DOM.$; @@ -55,7 +56,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'; @@ -458,12 +459,13 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor ariaLabel: localize('keybindingsLabel', "Keybindings"), setRowLineHeight: false, horizontalScrolling: false, + accessibilityProvider: new AccessibilityProvider(), overrideStyles: { listBackground: editorBackground } })) 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 +553,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(); } } @@ -826,7 +828,6 @@ class KeybindingItemRenderer implements IListRenderer this.keybindingsEditor.layoutColumns(elements)); - parent.setAttribute('aria-labelledby', elements.map(e => e.getAttribute('id')).join(' ')); return { parent, @@ -854,7 +855,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 +868,7 @@ class ActionsColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, @IKeybindingService private keybindingsService: IKeybindingService ) { super(keybindingsEditor); @@ -921,7 +922,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 })); @@ -933,7 +934,6 @@ class CommandColumn extends Column { const commandIdMatched = !!(keybindingItem.commandLabel && keybindingItemEntry.commandIdMatches); const commandDefaultLabelMatched = !!keybindingItemEntry.commandDefaultLabelMatches; DOM.toggleClass(this.commandColumn, 'vertical-align-column', commandIdMatched || commandDefaultLabelMatched); - this.commandColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); let commandLabel: HighlightedLabel | undefined; if (keybindingItem.commandLabel) { commandLabel = new HighlightedLabel(this.commandColumn, false); @@ -951,10 +951,6 @@ class CommandColumn extends Column { commandLabel.element.title = keybindingItem.commandLabel ? localize('title', "{0} ({1})", keybindingItem.commandLabel, keybindingItem.command) : keybindingItem.command; } } - - private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string { - return localize('commandAriaLabel', "Command is {0}.", keybindingItemEntry.keybindingItem.commandLabel ? keybindingItemEntry.keybindingItem.commandLabel : keybindingItemEntry.keybindingItem.command); - } } class KeybindingColumn extends Column { @@ -964,7 +960,7 @@ class KeybindingColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, ) { super(keybindingsEditor); @@ -974,15 +970,10 @@ class KeybindingColumn extends Column { render(keybindingItemEntry: IKeybindingItemEntry): void { DOM.clearNode(this.keybindingLabel); - this.keybindingLabel.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); if (keybindingItemEntry.keybindingItem.keybinding) { new KeybindingLabel(this.keybindingLabel, OS).set(keybindingItemEntry.keybindingItem.keybinding, keybindingItemEntry.keybindingMatches); } } - - private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string { - return keybindingItemEntry.keybindingItem.keybinding ? localize('keybindingAriaLabel', "Keybinding is {0}.", keybindingItemEntry.keybindingItem.keybinding.getAriaLabel()) : localize('noKeybinding', "No Keybinding assigned."); - } } class SourceColumn extends Column { @@ -992,7 +983,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 })); @@ -1000,13 +991,8 @@ class SourceColumn extends Column { render(keybindingItemEntry: IKeybindingItemEntry): void { DOM.clearNode(this.sourceColumn); - this.sourceColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); new HighlightedLabel(this.sourceColumn, false).set(keybindingItemEntry.keybindingItem.source, keybindingItemEntry.sourceMatches); } - - private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string { - return localize('sourceAriaLabel', "Source is {0}.", keybindingItemEntry.keybindingItem.source); - } } class WhenColumn extends Column { @@ -1024,7 +1010,7 @@ class WhenColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService private readonly themeService: IThemeService ) { @@ -1096,7 +1082,6 @@ class WhenColumn extends Column { } }, this, this.renderDisposables); this.whenInput.value = keybindingItemEntry.keybindingItem.when || ''; - this.whenLabel.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); DOM.toggleClass(this.whenLabel, 'code', !!keybindingItemEntry.keybindingItem.when); DOM.toggleClass(this.whenLabel, 'empty', !keybindingItemEntry.keybindingItem.when); if (keybindingItemEntry.keybindingItem.when) { @@ -1117,13 +1102,21 @@ class WhenColumn extends Column { this.keybindingsEditor.selectKeybinding(keybindingItemEntry); }, this, this.renderDisposables); } - - private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string { - return keybindingItemEntry.keybindingItem.when ? localize('whenAriaLabel', "When is {0}.", keybindingItemEntry.keybindingItem.when) : localize('noWhen', "No when context."); - } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +class AccessibilityProvider implements IListAccessibilityProvider { + + getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string { + let ariaLabel = localize('commandAriaLabel', "Command is {0}.", keybindingItemEntry.keybindingItem.commandLabel ? keybindingItemEntry.keybindingItem.commandLabel : keybindingItemEntry.keybindingItem.command); + ariaLabel += keybindingItemEntry.keybindingItem.keybinding ? localize('keybindingAriaLabel', "Keybinding is {0}.", keybindingItemEntry.keybindingItem.keybinding.getAriaLabel()) : localize('noKeybinding', "No Keybinding assigned."); + ariaLabel += localize('sourceAriaLabel', "Source is {0}.", keybindingItemEntry.keybindingItem.source); + ariaLabel += keybindingItemEntry.keybindingItem.when ? localize('whenAriaLabel', "When is {0}.", keybindingItemEntry.keybindingItem.when) : localize('noWhen', "No when context."); + return ariaLabel; + } + +} + +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..70b0774c711 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -64,10 +64,19 @@ 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; } +.settings-editor > .settings-header > .settings-header-controls .last-synced-label { + padding-top: 7px; + opacity: 0.9; +} + .settings-editor .settings-tabs-widget > .monaco-action-bar .action-item .action-details { opacity: 0.9; } @@ -314,8 +323,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/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css index c78c447a370..f4980a48476 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css @@ -13,16 +13,16 @@ } /* Deal with overflow */ -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value, -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling { - white-space: nowrap; +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value, +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling { + white-space: pre; overflow: hidden; text-overflow: ellipsis; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value { +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value { max-width: 90%; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling { +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling { max-width: 10%; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 316201a3f8a..89e29b8a4a1 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/preferences'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; 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( @@ -196,7 +197,7 @@ class DefaultPreferencesEditorInputFactory implements IEditorInputFactory { serialize(editorInput: EditorInput): string { const input = editorInput; - const serialized: ISerializedDefaultPreferencesEditorInput = { resource: input.getResource().toString() }; + const serialized: ISerializedDefaultPreferencesEditorInput = { resource: input.resource.toString() }; return JSON.stringify(serialized); } @@ -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 60bcbfd4ba4..3abc1d249ec 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -12,7 +12,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { IStringDictionary } from 'vs/base/common/collections'; import { getErrorMessage, isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { ArrayNavigator } from 'vs/base/common/iterator'; +import { ArrayNavigator } from 'vs/base/common/navigator'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -29,7 +29,7 @@ import { SelectionHighlighter } from 'vs/editor/contrib/multicursor/multicursor' import * as nls from 'vs/nls'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -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); @@ -609,7 +609,7 @@ class PreferencesRenderersController extends Disposable { const message = getErrorMessage(err).trim(); if (message && message !== 'Error') { // "Error" = any generic network error - this.telemetryService.publicLog('defaultSettings.searchError', { message }); + this.telemetryService.publicLog('defaultSettings.searchError', { message }, true); this.logService.info('Setting search error: ' + message); } return undefined; @@ -840,10 +840,10 @@ class SideBySidePreferencesWidget extends Widget { setInput(defaultPreferencesEditorInput: DefaultPreferencesEditorInput, editablePreferencesEditorInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<{ defaultPreferencesRenderer?: IPreferencesRenderer, editablePreferencesRenderer?: IPreferencesRenderer; }> { this.getOrCreateEditablePreferencesEditor(editablePreferencesEditorInput); - this.settingsTargetsWidget.settingsTarget = this.getSettingsTarget(editablePreferencesEditorInput.getResource()!); + this.settingsTargetsWidget.settingsTarget = this.getSettingsTarget(editablePreferencesEditorInput.resource!); return Promise.all([ - this.updateInput(this.defaultPreferencesEditor, defaultPreferencesEditorInput, DefaultSettingsEditorContribution.ID, editablePreferencesEditorInput.getResource()!, options, token), - this.updateInput(this.editablePreferencesEditor!, editablePreferencesEditorInput, SettingsEditorContribution.ID, defaultPreferencesEditorInput.getResource()!, options, token) + this.updateInput(this.defaultPreferencesEditor, defaultPreferencesEditorInput, DefaultSettingsEditorContribution.ID, editablePreferencesEditorInput.resource!, options, token), + this.updateInput(this.editablePreferencesEditor!, editablePreferencesEditorInput, SettingsEditorContribution.ID, defaultPreferencesEditorInput.resource!, options, token) ]) .then(([defaultPreferencesRenderer, editablePreferencesRenderer]) => { if (token.isCancellationRequested) { @@ -984,7 +984,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { private static _getContributions(): IEditorContributionDescription[] { const skipContributions = [FoldingController.ID, SelectionHighlighter.ID, FindController.ID]; const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1); - contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution }); + contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution as IConstructorSignature1 }); return contributions; } 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 392db681d09..87b52a7d06d 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 { Iterable } 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,12 +53,11 @@ 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'; +import { Emitter } from 'vs/base/common/event'; -function createGroupIterator(group: SettingsTreeGroupElement): Iterator> { - const groupsIt = Iterator.fromArray(group.children); - - return Iterator.map(groupsIt, g => { +function createGroupIterator(group: SettingsTreeGroupElement): Iterable> { + return Iterable.map(group.children, g => { return { element: g, children: g instanceof SettingsTreeGroupElement ? @@ -67,6 +73,9 @@ interface IFocusEventFromScroll extends KeyboardEvent { fromScroll: true; } +const searchBoxLabel = localize('SearchSettings.AriaLabel', "Search settings"); + +const SETTINGS_AUTOSAVE_NOTIFIED_KEY = 'hasNotifiedOfSettingsAutosave'; const SETTINGS_EDITOR_STATE_KEY = 'settingsEditorState'; export class SettingsEditor2 extends BaseEditor { @@ -77,7 +86,7 @@ export class SettingsEditor2 extends BaseEditor { private static CONFIG_SCHEMA_UPDATE_DELAYER = 500; private static readonly SUGGESTIONS: string[] = [ - `@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', `@${EXTENSION_SETTING_TAG}` + `@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', '@tag:sync', `@${EXTENSION_SETTING_TAG}` ]; private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean { @@ -157,7 +166,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 +194,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 +359,10 @@ export class SettingsEditor2 extends BaseEditor { } } + focusTOC(): void { + this.tocTree.domFocus(); + } + showContextMenu(): void { const activeElement = this.getActiveElementInSettingsTree(); if (!activeElement) { @@ -391,6 +408,13 @@ export class SettingsEditor2 extends BaseEditor { this.searchWidget.setValue(query.trim()); } + private updateInputAriaLabel(lastSyncedLabel: string) { + const label = lastSyncedLabel ? + `${searchBoxLabel}. ${lastSyncedLabel}` : + searchBoxLabel; + this.searchWidget.updateAriaLabel(label); + } + private createHeader(parent: HTMLElement): void { this.headerContainer = DOM.append(parent, $('.settings-header')); @@ -398,7 +422,6 @@ export class SettingsEditor2 extends BaseEditor { const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), 'codicon-clear-all', false, () => { this.clearSearchResults(); return Promise.resolve(null); }); - const searchBoxLabel = localize('SearchSettings.AriaLabel', "Search settings"); this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, searchContainer, { triggerCharacters: ['@'], provideResults: (query: string) => { @@ -445,6 +468,11 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTargetsWidget.settingsTarget = ConfigurationTarget.USER_LOCAL; this.settingsTargetsWidget.onDidTargetChange(target => this.onDidSettingsTargetChange(target)); + if (syncAllowed(this.productService, this.configurationService)) { + const syncControls = this._register(this.instantiationService.createInstance(SyncControls, headerControlsContainer)); + this._register(syncControls.onDidChangeLastSyncedLabel(lastSyncedLabel => this.updateInputAriaLabel(lastSyncedLabel))); + } + this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); const actionBar = this._register(new ActionBar(this.controlsElement, { @@ -493,15 +521,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 +687,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 +728,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.")); } } @@ -1331,7 +1358,7 @@ export class SettingsEditor2 extends BaseEditor { const message = getErrorMessage(err).trim(); if (message && message !== 'Error') { // "Error" = any generic network error - this.telemetryService.publicLog('settingsEditor.searchError', { message }); + this.telemetryService.publicLog('settingsEditor.searchError', { message }, true); this.logService.info('Setting search error: ' + message); } return null; @@ -1363,7 +1390,87 @@ export class SettingsEditor2 extends BaseEditor { } } +class SyncControls extends Disposable { + private readonly lastSyncedLabel!: HTMLElement; + private readonly turnOnSyncButton!: Button; + + private readonly _onDidChangeLastSyncedLabel = this._register(new Emitter()); + public readonly onDidChangeLastSyncedLabel = this._onDidChangeLastSyncedLabel.event; + + 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; + let label: string; + if (typeof last === 'number') { + const d = fromNow(last, true); + label = localize('lastSyncedLabel', "Last synced: {0}", d); + } else { + label = ''; + } + + this.lastSyncedLabel.textContent = label; + this._onDidChangeLastSyncedLabel.fire(label); + } + + 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..335e4c7cedf 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"), @@ -179,6 +184,11 @@ export const tocData: ITOCEntry = { id: 'features/timeline', label: localize('timeline', "Timeline"), settings: ['timeline.*'] + }, + { + id: 'features/notebook', + label: localize('notebook', 'Notebook'), + settings: ['notebook.*'] } ] }, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index ca5fb320cbb..ec2d1aa938e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -13,7 +13,7 @@ import { alert as ariaAlert } from 'vs/base/browser/ui/aria/aria'; import { Button } from 'vs/base/browser/ui/button/button'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { CachedListVirtualDelegate, ListAriaRootRole } from 'vs/base/browser/ui/list/list'; +import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DefaultStyleController } from 'vs/base/browser/ui/list/listWidget'; import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; @@ -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'; @@ -49,7 +49,9 @@ import { ExcludeSettingWidget, IListChangeEvent, IListDataItem, ListSettingWidge import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; -import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncEnablementService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; +import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; +import { getInvalidTypeError } from 'vs/workbench/services/preferences/common/preferencesValidation'; const $ = DOM.$; @@ -204,6 +206,7 @@ interface ISettingItemTemplate extends IDisposableTemplate { controlElement: HTMLElement; deprecationWarningElement: HTMLElement; otherOverridesElement: HTMLElement; + syncIgnoredElement: HTMLElement; toolbar: ToolBar; elementDisposables: IDisposable[]; } @@ -226,6 +229,7 @@ interface ISettingEnumItemTemplate extends ISettingItemTemplate { interface ISettingComplexItemTemplate extends ISettingItemTemplate { button: Button; + validationErrorMessageElement: HTMLElement; } interface ISettingListItemTemplate extends ISettingItemTemplate { @@ -300,6 +304,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 +319,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 +340,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 +358,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 +385,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre controlElement, deprecationWarningElement, otherOverridesElement, + syncIgnoredElement, toolbar }; @@ -447,8 +475,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 +512,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 { @@ -670,9 +708,13 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I buttonForeground: 'foreground' })); + const validationErrorMessageElement = $('.setting-item-validation-message'); + common.containerElement.appendChild(validationErrorMessageElement); + const template: ISettingComplexItemTemplate = { ...common, - button: openSettingsButton + button: openSettingsButton, + validationErrorMessageElement }; this.addSettingElementFocusHandler(template); @@ -684,8 +726,20 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I super.renderSettingElement(element, index, templateData); } - protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingExcludeItemTemplate, onChange: (value: string) => void): void { + protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate, onChange: (value: string) => void): void { template.onChange = () => this._onDidOpenSettings.fire(dataElement.setting.key); + this.renderValidations(dataElement, template); + } + + private renderValidations(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate) { + const errMsg = getInvalidTypeError(dataElement.value, dataElement.setting.type); + if (errMsg) { + DOM.addClass(template.containerElement, 'invalid-input'); + template.validationErrorMessageElement.innerText = errMsg; + return; + } + + DOM.removeClass(template.containerElement, 'invalid-input'); } } @@ -1085,6 +1139,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 +1193,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre descriptionElement, deprecationWarningElement, otherOverridesElement, + syncIgnoredElement, toolbar }; @@ -1237,8 +1293,11 @@ export class SettingTreeRenderers { private getActionsForSetting(setting: ISetting): IAction[] { const enableSync = this._userDataSyncEnablementService.isEnabled(); - return enableSync ? - [this._instantiationService.createInstance(StopSyncingSettingAction, setting)] : + return enableSync && !setting.disallowSyncIgnore ? + [ + new Separator(), + this._instantiationService.createInstance(SyncSettingAction, setting) + ] : []; } @@ -1419,6 +1478,11 @@ class SettingsTreeDelegate extends CachedListVirtualDelegate { renderers, { supportDynamicHeights: true, - ariaRole: ListAriaRootRole.FORM, + ariaRole: 'form', ariaLabel: localize('treeAriaLabel', "Settings"), identityProvider: { getId(e) { @@ -1505,7 +1569,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. @@ -1622,31 +1686,46 @@ class CopySettingAsJSONAction extends Action { } } -class StopSyncingSettingAction extends Action { +class SyncSettingAction extends Action { static readonly ID = 'settings.stopSyncingSetting'; - static readonly LABEL = localize('stopSyncingSetting', "Don't Sync This Setting"); + static readonly LABEL = localize('stopSyncingSetting', "Sync This Setting"); constructor( private readonly setting: ISetting, @IConfigurationService private readonly configService: IConfigurationService, ) { - super(StopSyncingSettingAction.ID, StopSyncingSettingAction.LABEL); + super(SyncSettingAction.ID, SyncSettingAction.LABEL); + this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.ignoredSettings'))(() => this.update())); this.update(); } - update() { - const ignoredSettings = getIgnoredSettings(this.configService); - this.checked = ignoredSettings.includes(this.setting.key); + async update() { + const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this.configService); + this.checked = !ignoredSettings.includes(this.setting.key); } async run(): Promise { - const currentValue = this.configService.getValue('sync.ignoredSettings'); - if (this.checked) { - this.configService.updateValue('sync.ignoredSettings', currentValue.filter(v => v !== this.setting.key)); - } else { - this.configService.updateValue('sync.ignoredSettings', [...currentValue, this.setting.key]); + // first remove the current setting completely from ignored settings + let currentValue = [...this.configService.getValue('sync.ignoredSettings')]; + currentValue = currentValue.filter(v => v !== this.setting.key && v !== `-${this.setting.key}`); + + const defaultIgnoredSettings = getDefaultIgnoredSettings(); + const isDefaultIgnored = defaultIgnoredSettings.includes(this.setting.key); + const askedToSync = !this.checked; + + // If asked to sync, then add only if it is ignored by default + if (askedToSync && isDefaultIgnored) { + currentValue.push(`-${this.setting.key}`); } + // If asked not to sync, then add only if it is not ignored by default + if (!askedToSync && !isDefaultIgnored) { + currentValue.push(this.setting.key); + } + + this.configService.updateValue('sync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER); + return Promise.resolve(undefined); } + } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index cc60be57781..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; }`); @@ -443,12 +443,12 @@ export class ListSettingWidget extends Disposable { const onSubmit = (edited: boolean) => { this.model.setEditKey('none'); - const value = valueInput.value.trim(); + const value = valueInput.value; if (edited && !isUndefinedOrNull(value)) { this._onDidChangeList.fire({ originalValue: item.value, value: value, - sibling: siblingInput && siblingInput.value.trim(), + sibling: siblingInput && siblingInput.value, targetIndex: idx }); } diff --git a/src/vs/workbench/contrib/preferences/browser/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts index 1120b48feca..cbe46dff65a 100644 --- a/src/vs/workbench/contrib/preferences/browser/tocTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts @@ -5,10 +5,10 @@ import * as DOM from 'vs/base/browser/dom'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { DefaultStyleController, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; -import { Iterator } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { editorBackground, transparent, foreground } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler } from 'vs/platform/theme/common/styler'; @@ -138,11 +138,10 @@ class TOCTreeDelegate implements IListVirtualDelegate { } } -export function createTOCIterator(model: TOCTreeModel | SettingsTreeGroupElement, tree: TOCTree): Iterator> { +export function createTOCIterator(model: TOCTreeModel | SettingsTreeGroupElement, tree: TOCTree): Iterable> { const groupChildren = model.children.filter(c => c instanceof SettingsTreeGroupElement); - const groupsIt = Iterator.fromArray(groupChildren); - return Iterator.map(groupsIt, g => { + return Iterable.map(groupChildren, g => { const hasGroupChildren = g.children.some(c => c instanceof SettingsTreeGroupElement); return { @@ -156,7 +155,7 @@ export function createTOCIterator(model: TOCTreeModel | SettingsTreeGroupElement }); } -class SettingsAccessibilityProvider implements IAccessibilityProvider { +class SettingsAccessibilityProvider implements IListAccessibilityProvider { getAriaLabel(element: SettingsTreeElement): string { if (!element) { return ''; 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/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index fef8e28c6c8..04945ccdee6 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -58,12 +58,14 @@ export class PreferencesContribution implements IWorkbenchContribution { // install editor opening listener unless user has disabled this if (!!this.configurationService.getValue(USE_SPLIT_JSON_SETTING)) { - this.editorOpeningListener = this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group)); + this.editorOpeningListener = this.editorService.overrideOpenEditor({ + open: (editor, options, group) => this.onEditorOpening(editor, options, group) + }); } } private onEditorOpening(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined { - const resource = editor.getResource(); + const resource = editor.resource; if ( !resource || !endsWith(resource.path, 'settings.json') || // resource must end in settings.json diff --git a/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts b/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts index 90a2e3a7bbb..16c83919b14 100644 --- a/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts +++ b/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts @@ -5,13 +5,13 @@ import * as assert from 'assert'; import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { Position } from 'vs/editor/common/core/position'; suite('SmartSnippetInserter', () => { function testSmartSnippetInserter(text: string[], runner: (assert: (desiredPos: Position, pos: Position, prepend: string, append: string) => void) => void): void { - let model = TextModel.createFromString(text.join('\n')); + let model = createTextModel(text.join('\n')); runner((desiredPos, pos, prepend, append) => { let actual = SmartSnippetInserter.insertSnippet(model, desiredPos); let expected = { 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 76b780adca8..acb4cdca610 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'; @@ -55,6 +55,8 @@ 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; @@ -374,8 +376,9 @@ class HelpPanel extends ViewPane { @IRemoteExplorerService protected readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService protected readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } protected renderBody(container: HTMLElement): void { @@ -394,6 +397,11 @@ class HelpPanel extends ViewPane { new HelpDataSource(), { keyboardSupport: true, + accessibilityProvider: { + getAriaLabel: (item: HelpItemBase) => { + return item.label; + } + } } ); @@ -583,26 +591,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)); } } } @@ -617,58 +699,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 = { @@ -688,9 +753,8 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { }; connection.onDidStateChange((e) => { - if (currentTimer) { - currentTimer.dispose(); - currentTimer = null; + if (visibleProgress) { + visibleProgress.stopTimer(); } if (disposableListener) { @@ -699,33 +763,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); } } }); @@ -733,7 +791,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 @@ -744,7 +801,6 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { break; case PersistentConnectionEventType.ConnectionGain: hideProgress(); - progressReporter = null; break; } }); @@ -752,35 +808,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 664565a92cf..9531c6c7474 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -24,11 +24,11 @@ import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose, Disp import { ActionBar, ActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { ActionRunner, IAction } from 'vs/base/common/actions'; -import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions'; 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'; @@ -40,6 +40,7 @@ import { URI } from 'vs/base/common/uri'; import { RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false); @@ -158,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; @@ -281,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 }; @@ -374,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( @@ -382,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, @@ -414,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); @@ -457,9 +454,10 @@ export class TunnelPanel extends ViewPane { @INotificationService private readonly notificationService: INotificationService, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService themeService: IThemeService, - @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService); this.tunnelViewFocusContext = TunnelViewFocusContextKey.bindTo(contextKeyService); @@ -509,7 +507,20 @@ export class TunnelPanel extends ViewPane { return item.label; } }, - multipleSelectionSupport: false + multipleSelectionSupport: false, + accessibilityProvider: { + getAriaLabel: (item: ITunnelItem | ITunnelGroup) => { + if (item instanceof TunnelItem) { + if (item.localAddress) { + return nls.localize('remote.tunnel.ariaLabelForwarded', "Remote port {0}:{1} forwarded to local address {2}", item.remoteHost, item.remotePort, item.localAddress); + } else { + return nls.localize('remote.tunnel.ariaLabelCandidate', "Remote port {0}:{1} not forwarded", item.remoteHost, item.remotePort); + } + } else { + return item.label; + } + } + } } ); const actionRunner: ActionRunner = new ActionRunner(); @@ -575,7 +586,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(); @@ -598,7 +609,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); @@ -659,6 +670,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"); @@ -692,7 +714,7 @@ const invalidPortNumberString: string = nls.localize('remote.tunnelsView.portNum namespace ForwardPortAction { export const INLINE_ID = 'remote.tunnel.forwardInline'; export const COMMANDPALETTE_ID = 'remote.tunnel.forwardCommandPalette'; - export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port"); + export const LABEL: ILocalizedString = { value: nls.localize('remote.tunnel.forward', "Forward a Port"), original: 'Forward a Port' }; export const TREEITEM_LABEL = nls.localize('remote.tunnel.forwardItem', "Forward Port"); const forwardPrompt = nls.localize('remote.tunnel.forwardPrompt', "Port number or address (eg. 3000 or 10.10.10.10:2000)."); @@ -735,7 +757,7 @@ namespace ForwardPortAction { } remoteExplorerService.setEditable(undefined, null); }, - validationMessage: validateInput, + validationMessage: (value) => validationMessage(validateInput(value)), placeholder: forwardPrompt }); } @@ -776,7 +798,7 @@ function makeTunnelPicks(tunnels: Tunnel[]): QuickPickInput[] { }); if (picks.length === 0) { picks.push({ - label: nls.localize('remote.tunnel.closeNoPorts', "No ports currently forwarded. Try running the {0} command", ForwardPortAction.LABEL) + label: nls.localize('remote.tunnel.closeNoPorts', "No ports currently forwarded. Try running the {0} command", ForwardPortAction.LABEL.value) }); } return picks; @@ -785,7 +807,7 @@ function makeTunnelPicks(tunnels: Tunnel[]): QuickPickInput[] { namespace ClosePortAction { export const INLINE_ID = 'remote.tunnel.closeInline'; export const COMMANDPALETTE_ID = 'remote.tunnel.closeCommandPalette'; - export const LABEL = nls.localize('remote.tunnel.close', "Stop Forwarding Port"); + export const LABEL: ILocalizedString = { value: nls.localize('remote.tunnel.close', "Stop Forwarding Port"), original: 'Stop Forwarding Port' }; export function inlineHandler(): ICommandHandler { return async (accessor, arg) => { @@ -918,7 +940,7 @@ namespace ChangeLocalPortAction { } } }, - validationMessage: validateInput, + validationMessage: (value) => validationMessage(validateInput(value)), placeholder: nls.localize('remote.tunnelsView.changePort', "New local port") }); } @@ -1037,7 +1059,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 a1219d0d8c2..ab9cf63219d 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -33,6 +33,7 @@ import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/vie import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export interface ISpliceEvent { index: number; @@ -185,13 +186,14 @@ export class MainPane extends ViewPane { @IContextMenuService protected contextMenuService: IContextMenuService, @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, @IConfigurationService configurationService: IConfigurationService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } protected renderBody(container: HTMLElement): void { @@ -201,17 +203,22 @@ export class MainPane extends ViewPane { const renderer = this.instantiationService.createInstance(ProviderRenderer); const identityProvider = { getId: (r: ISCMRepository) => r.provider.id }; - this.list = >this.instantiationService.createInstance(WorkbenchList, `SCM Main`, container, delegate, [renderer], { + this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, container, delegate, [renderer], { identityProvider, horizontalScrolling: false, overrideStyles: { listBackground: SIDE_BAR_BACKGROUND + }, + accessibilityProvider: { + getAriaLabel(r: ISCMRepository) { + return r.provider.label; + } } - }); + }) as WorkbenchList; 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 482b66fb18d..5ea3fd924b9 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -8,17 +8,6 @@ flex: 1; } -.scm-viewlet .empty-message { - box-sizing: border-box; - height: 100%; - padding: 10px 22px 0 22px; -} - -.scm-viewlet:not(.empty) .empty-message, -.scm-viewlet.empty .monaco-pane-view { - display: none; -} - .scm-viewlet .scm-status { height: 100%; position: relative; @@ -164,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 3f65b434ea8..b7dc095e52c 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -24,18 +24,18 @@ 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'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { WorkbenchCompressibleObjectTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { disposableTimeout, ThrottledDelayer } from 'vs/base/common/async'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree'; import { ISequence, ISplice } from 'vs/base/common/sequence'; import { ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree'; -import { Iterator } from 'vs/base/common/iterator'; +import { Iterable } from 'vs/base/common/iterator'; import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { URI } from 'vs/base/common/uri'; import { FileKind } from 'vs/platform/files/common/files'; @@ -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'; @@ -67,7 +66,13 @@ import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { Schemas } from 'vs/base/common/network'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ModesHoverController } from 'vs/editor/contrib/hover/hover'; +import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; +import { LinkDetector } from 'vs/editor/contrib/links/links'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IModeService } from 'vs/editor/common/services/modeService'; type TreeElement = ISCMResourceGroup | IResourceNode | ISCMResource; @@ -146,13 +151,13 @@ interface ResourceTemplate { disposables: IDisposable; } -class MultipleSelectionActionRunner extends ActionRunner { +class RepositoryPaneActionRunner extends ActionRunner { constructor(private getSelectedResources: () => (ISCMResource | IResourceNode)[]) { super(); } - runAction(action: IAction, context: ISCMResource | IResourceNode): Promise { + async runAction(action: IAction, context: ISCMResource | IResourceNode): Promise { if (!(action instanceof MenuItemAction)) { return super.runAction(action, context); } @@ -161,7 +166,7 @@ class MultipleSelectionActionRunner extends ActionRunner { const contextIsSelected = selection.some(s => s === context); const actualContext = contextIsSelected ? selection : [context]; const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e])); - return action.run(...args); + await action.run(...args); } } @@ -174,7 +179,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer ViewModel, private labels: ResourceLabels, private actionViewItemProvider: IActionViewItemProvider, - private getSelectedResources: () => (ISCMResource | IResourceNode)[], + private actionRunner: ActionRunner, private themeService: IThemeService, private menus: SCMMenus ) { } @@ -186,7 +191,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer { } } +export class SCMAccessibilityProvider implements IListAccessibilityProvider { + + getAriaLabel(element: TreeElement): string { + if (ResourceTree.isResourceNode(element)) { + return element.name; + } else if (isSCMResourceGroup(element)) { + return element.label; + } else { + return `${basename(element.sourceUri)}, ${element.decorations.tooltip || ''}`; + } + } +} + interface IGroupItem { readonly group: ISCMResourceGroup; readonly resources: ISCMResource[]; @@ -394,8 +412,8 @@ interface IGroupItem { function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode): ICompressedTreeElement { const children = mode === ViewModelMode.List - ? Iterator.map(Iterator.fromArray(item.resources), element => ({ element, incompressible: true })) - : Iterator.map(item.tree.root.children, node => asTreeElement(node, true)); + ? Iterable.map(item.resources, element => ({ element, incompressible: true })) + : Iterable.map(item.tree.root.children, node => asTreeElement(node, true)); return { element: item.group, children, incompressible: true, collapsible: true }; } @@ -403,7 +421,7 @@ function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode): ICompres function asTreeElement(node: IResourceNode, forceIncompressible: boolean): ICompressedTreeElement { return { element: (node.childrenCount === 0 && node.element) ? node.element : node, - children: Iterator.map(node.children, node => asTreeElement(node, false)), + children: Iterable.map(node.children, node => asTreeElement(node, false)), incompressible: !!node.element || forceIncompressible }; } @@ -615,13 +633,11 @@ export class RepositoryPane extends ViewPane { protected contextKeyService: IContextKeyService; private commitTemplate = ''; - isEmpty() { return true; } - constructor( readonly repository: ISCMRepository, options: IViewPaneOptions, @IKeybindingService protected keybindingService: IKeybindingService, - @IWorkbenchThemeService protected themeService: IWorkbenchThemeService, + @IThemeService protected themeService: IThemeService, @IContextMenuService protected contextMenuService: IContextMenuService, @IContextViewService protected contextViewService: IContextViewService, @ICommandService protected commandService: ICommandService, @@ -634,9 +650,11 @@ export class RepositoryPane extends ViewPane { @IMenuService protected menuService: IMenuService, @IStorageService private storageService: IStorageService, @IModelService private modelService: IModelService, + @IModeService private modeService: IModeService, @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider); this._register(this.menus); @@ -685,6 +703,7 @@ export class RepositoryPane extends ViewPane { const label = binding ? binding.getLabel() : (platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'); const placeholderText = format(this.repository.input.placeholder, label); + this.inputEditor.updateOptions({ ariaLabel: placeholderText }); placeholderTextContainer.textContent = placeholderText; }; @@ -730,7 +749,7 @@ export class RepositoryPane extends ViewPane { wrappingStrategy: 'advanced', wrappingIndent: 'none', padding: { top: 3, bottom: 3 }, - suggest: { showWords: false } + quickSuggestions: false }; const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { @@ -741,6 +760,9 @@ export class RepositoryPane extends ViewPane { MenuPreventer.ID, SelectionClipboardContributionID, ContextMenuController.ID, + ColorDetector.ID, + ModesHoverController.ID, + LinkDetector.ID ]) }; @@ -765,7 +787,10 @@ export class RepositoryPane extends ViewPane { query }); - this.inputModel = this.modelService.getModel(uri) || this.modelService.createModel('', null, uri); + this.configurationService.updateValue('editor.wordBasedSuggestions', false, { resource: uri }, ConfigurationTarget.MEMORY); + + const mode = this.modeService.create('scminput'); + this.inputModel = this.modelService.getModel(uri) || this.modelService.createModel('', mode, uri); this.inputEditor.setModel(this.inputModel); this._register(this.inputEditor.onDidChangeCursorPosition(triggerValidation)); @@ -777,6 +802,7 @@ export class RepositoryPane extends ViewPane { return; } this.inputModel.setValue(value); + this.inputEditor.setPosition(this.inputModel.getFullModelRange().getEndPosition()); })); // Keep API in sync with model and update placeholder and validation @@ -818,9 +844,19 @@ export class RepositoryPane extends ViewPane { this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.listLabels); + const actionRunner = new RepositoryPaneActionRunner(() => 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.themeService, this.menus) + new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, actionRunner, this.themeService, this.menus) ]; const filter = new SCMTreeFilter(); @@ -828,7 +864,7 @@ export class RepositoryPane extends ViewPane { const keyboardNavigationLabelProvider = new SCMTreeKeyboardNavigationLabelProvider(); const identityProvider = new SCMResourceIdentityProvider(); - this.tree = >this.instantiationService.createInstance( + this.tree = this.instantiationService.createInstance( WorkbenchCompressibleObjectTree, 'SCM Tree Repo', this.listContainer, @@ -842,13 +878,12 @@ export class RepositoryPane extends ViewPane { keyboardNavigationLabelProvider, overrideStyles: { listBackground: SIDE_BAR_BACKGROUND - } - }); + }, + accessibilityProvider: new SCMAccessibilityProvider() + }) as WorkbenchCompressibleObjectTree; - this._register(Event.chain(this.tree.onDidOpen) - .map(e => e.elements[0]) - .filter((e): e is ISCMResource => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e)) - .on(this.open, this)); + const navigator = this._register(new TreeResourceNavigator(this.tree, { openOnSelection: false })); + this._register(navigator.onDidOpenResource(this.open, this)); this._register(Event.chain(this.tree.onDidPin) .map(e => e.elements[0]) @@ -858,7 +893,7 @@ export class RepositoryPane extends ViewPane { this._register(this.tree.onContextMenu(this.onListContextMenu, this)); this._register(this.tree); - let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree; + let viewMode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree; const rootUri = this.repository.provider.rootUri; @@ -866,11 +901,11 @@ export class RepositoryPane extends ViewPane { const storageMode = this.storageService.get(`scm.repository.viewMode:${rootUri.toString()}`, StorageScope.WORKSPACE) as ViewModelMode; if (typeof storageMode === 'string') { - mode = storageMode; + viewMode = storageMode; } } - this.viewModel = this.instantiationService.createInstance(ViewModel, this.repository.provider.groups, this.tree, mode); + this.viewModel = this.instantiationService.createInstance(ViewModel, this.repository.provider.groups, this.tree, viewMode); this._register(this.viewModel); addClass(this.listContainer, 'file-icon-themable-tree'); @@ -988,15 +1023,19 @@ export class RepositoryPane extends ViewPane { return this.repository.provider; } - private open(e: ISCMResource): void { - e.open(); + private open(e: IOpenEvent): void { + if (!e.element || isSCMResourceGroup(e.element) || ResourceTree.isResourceNode(e.element)) { + return; + } + + e.element.open(!!e.editorOptions.preserveFocus); } 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); } } @@ -1020,11 +1059,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 MultipleSelectionActionRunner(() => this.getSelectedResources()) + actionRunner }); } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index fd371ac79b0..ca87f4502b4 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -26,6 +26,7 @@ import { SCMService } from 'vs/workbench/contrib/scm/common/scmService'; import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; import { SCMViewPaneContainer } from 'vs/workbench/contrib/scm/browser/scmViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; class OpenSCMViewletAction extends ShowViewletAction { @@ -37,6 +38,12 @@ class OpenSCMViewletAction extends ShowViewletAction { } } +ModesRegistry.registerLanguage({ + id: 'scminput', + extensions: [], + mimetypes: ['text/x-scm-input'] +}); + Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(DirtyDiffWorkbenchController, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 128c0029152..1d3fdd35ad5 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -6,12 +6,11 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { append, $, toggleClass, addClasses } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; @@ -25,13 +24,16 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsRegistry, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions, IViewDescriptorService, IViewDescriptor } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; import { MainPaneDescriptor, MainPane, IViewModel } from 'vs/workbench/contrib/scm/browser/mainPane'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPaneContainer, IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import type { IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { debounce } from 'vs/base/common/decorators'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { addClass } from 'vs/base/browser/dom'; export interface ISpliceEvent { index: number; @@ -39,12 +41,46 @@ export interface ISpliceEvent { elements: T[]; } +export class EmptyPane extends ViewPane { + + static readonly ID = 'workbench.scm'; + static readonly TITLE = localize('scm', "Source Control"); + + constructor( + options: IViewPaneOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + } + + shouldShowWelcome(): boolean { + return true; + } +} + +export class EmptyPaneDescriptor implements IViewDescriptor { + readonly id = EmptyPane.ID; + readonly name = EmptyPane.TITLE; + readonly ctorDescriptor = new SyncDescriptor(EmptyPane); + readonly canToggleVisibility = true; + readonly hideByDefault = false; + readonly order = -1000; + readonly workspace = true; + readonly when = ContextKeyExpr.equals('scm.providerCount', 0); +} + export class SCMViewPaneContainer extends ViewPaneContainer implements IViewModel { private static readonly STATE_KEY = 'workbench.scm.views.state'; - private el!: HTMLElement; - private message: HTMLElement; private menus: SCMMenus; private _repositories: ISCMRepository[] = []; @@ -94,9 +130,14 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); - this.message = $('.empty-message', { tabIndex: 0 }, localize('no open repo', "No source control providers registered.")); - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + + viewsRegistry.registerViewWelcomeContent(EmptyPane.ID, { + content: localize('no open repo', "No source control providers registered."), + when: 'default' + }); + + viewsRegistry.registerViews([new EmptyPaneDescriptor()], this.viewContainer); viewsRegistry.registerViews([new MainPaneDescriptor(this)], this.viewContainer); this._register(configurationService.onDidChangeConfiguration(e => { @@ -113,11 +154,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode create(parent: HTMLElement): void { super.create(parent); - - this.el = parent; - addClasses(parent, 'scm-viewlet', 'empty'); - append(parent, this.message); - + addClass(parent, 'scm-viewlet'); this._register(this.scmService.onDidAddRepository(this.onDidAddRepository, this)); this._register(this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this)); this.scmService.repositories.forEach(r => this.onDidAddRepository(r)); @@ -156,9 +193,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } private onDidChangeRepositories(): void { - const repositoryCount = this.repositories.length; - toggleClass(this.el, 'empty', repositoryCount === 0); - this.repositoryCountKey.set(repositoryCount); + this.repositoryCountKey.set(this.repositories.length); } private onDidShowView(e: IAddedViewDescriptorRef[]): void { @@ -187,23 +222,19 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode } focus(): void { - if (this.repositoryCountKey.get()! === 0) { - this.message.focus(); - } else { - const repository = this.visibleRepositories[0]; + const repository = this.visibleRepositories[0]; - if (repository) { - const pane = this.panes - .filter(pane => pane instanceof RepositoryPane && pane.repository === repository)[0] as RepositoryPane | undefined; + if (repository) { + const pane = this.panes + .filter(pane => pane instanceof RepositoryPane && pane.repository === repository)[0] as RepositoryPane | undefined; - if (pane) { - pane.focus(); - } else { - super.focus(); - } + if (pane) { + pane.focus(); } else { super.focus(); } + } else { + super.focus(); } } diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index cf63ad255d9..d6fd58f1519 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -30,7 +30,7 @@ export interface ISCMResource { readonly resourceGroup: ISCMResourceGroup; readonly sourceUri: URI; readonly decorations: ISCMResourceDecorations; - open(): Promise; + open(preserveFocus: boolean): Promise; } export interface ISCMResourceGroup extends ISequence { 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..c2265d3ba01 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -0,0 +1,935 @@ +/*--------------------------------------------------------------------------------------------- + * 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()) + }; + } + } + + async restoreEditorViewState(): Promise { + if (this.editorViewState) { + await this.editorService.openEditor( + this.editorViewState.editor, + { viewState: this.editorViewState.state, preserveFocus: true /* import to not close the picker as a result */ }, + this.editorViewState.group + ); + } + } + }(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)(() => this.pickState.restoreEditorViewState())); + + // 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, forcePinned: 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, forcePinned: event.inBackground }) + }; + } + + private async openAnything(resourceOrEditor: URI | IEditorInput | IResourceEditorInput, options: { keyMods?: IKeyMods, preserveFocus?: boolean, range?: IRange, forceOpenSideBySide?: boolean, forcePinned?: boolean }): Promise { + const editorOptions: ITextEditorOptions = { + preserveFocus: options.preserveFocus, + pinned: options.keyMods?.alt || options.forcePinned || this.configuration.openEditorPinned, + selection: options.range ? Range.collapseToStart(options.range) : undefined + }; + + const targetGroup = options.keyMods?.ctrlCmd || options.forceOpenSideBySide ? SIDE_GROUP : ACTIVE_GROUP; + + // Restore any view state if the target is the side group + if (targetGroup === SIDE_GROUP) { + await this.pickState.restoreEditorViewState(); + } + + // Open editor + 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/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 0da45c12686..c015e1665f5 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -10,15 +10,12 @@ import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IInputValidator, HistoryInputBox, IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; -import { Delayer } from 'vs/base/common/async'; import type { IThemable } from 'vs/base/common/styler'; export interface IOptions { @@ -50,11 +47,8 @@ export class PatternInputWidget extends Widget implements IThemable { private _onCancel = this._register(new Emitter()); onCancel: CommonEvent = this._onCancel.event; - private searchOnTypeDelayer: Delayer; - constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService protected themeService: IThemeService, - @IConfigurationService private configurationService: IConfigurationService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); @@ -62,8 +56,6 @@ export class PatternInputWidget extends Widget implements IThemable { this.placeholder = options.placeholder || ''; this.ariaLabel = options.ariaLabel || nls.localize('defaultLabel', "input"); - this._register(this.searchOnTypeDelayer = new Delayer(this.searchConfig.searchOnTypeDebouncePeriod)); - this.render(options); parent.appendChild(this.domNode); @@ -152,6 +144,8 @@ export class PatternInputWidget extends Widget implements IThemable { history: options.history || [] }, this.contextKeyService); this._register(attachInputBoxStyler(this.inputBox, this.themeService)); + this._register(this.inputBox.onDidChange(() => this._onSubmit.fire(true))); + this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement); this.onkeyup(this.inputBox.inputElement, (keyboardEvent) => this.onInputKeyUp(keyboardEvent)); @@ -170,24 +164,13 @@ export class PatternInputWidget extends Widget implements IThemable { switch (keyboardEvent.keyCode) { case KeyCode.Enter: this.onSearchSubmit(); - this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(false), 0); + this._onSubmit.fire(false); return; case KeyCode.Escape: this._onCancel.fire(); return; - case KeyCode.Tab: case KeyCode.Tab | KeyMod.Shift: return; - default: - if (this.searchConfig.searchOnType) { - this._onCancel.fire(); - this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); - } - return; } } - - private get searchConfig() { - return this.configurationService.getValue('search'); - } } export class ExcludePatternInputWidget extends PatternInputWidget { @@ -197,10 +180,9 @@ export class ExcludePatternInputWidget extends PatternInputWidget { constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService ) { - super(parent, contextViewProvider, options, themeService, configurationService, contextKeyService); + super(parent, contextViewProvider, options, themeService, contextKeyService); } private useExcludesAndIgnoreFilesBox!: Checkbox; diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index aff890ade35..23b193da924 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -104,14 +104,7 @@ export class ReplaceService implements IReplaceService { const edits: WorkspaceTextEdit[] = this.createEdits(arg, resource); await this.bulkEditorService.apply({ edits }, { progress }); - return Promise.all(edits.map(e => { - const model = this.textFileService.files.get(e.resource); - if (model) { - return model.save(); - } - - return Promise.resolve(undefined); - })); + return Promise.all(edits.map(e => this.textFileService.files.get(e.resource)?.save())); } async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 97da9089fd0..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,15 +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 product from 'vs/platform/product/common/product'; 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); @@ -73,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(); @@ -81,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, @@ -455,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); @@ -557,8 +558,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { order: 1 }); -registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Next Search Result', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.and(Constants.HasSearchResults)), 'Focus Previous Search Result', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextSearchResultAction, FocusNextSearchResultAction.ID, FocusNextSearchResultAction.LABEL, { primary: KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Next Search Result', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousSearchResultAction, FocusPreviousSearchResultAction.ID, FocusPreviousSearchResultAction.LABEL, { primary: KeyMod.Shift | KeyCode.F4 }, ContextKeyExpr.or(Constants.HasSearchResults, SearchEditorConstants.InSearchEditor)), 'Focus Previous Search Result', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ReplaceInFilesAction, ReplaceInFilesAction.ID, ReplaceInFilesAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_H }), 'Replace in Files', category); MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { @@ -612,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); @@ -647,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: [ @@ -705,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."), @@ -732,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."), '', '' ], @@ -775,21 +778,16 @@ configurationRegistry.registerConfiguration({ default: 300, markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.") }, - 'search.enableSearchEditorPreview': { - type: 'boolean', - default: product.quality !== 'stable', - description: nls.localize('search.enableSearchEditorPreview', "Experimental: When enabled, allows opening workspace search results in an editor.") - }, - 'search.searchEditorPreview.doubleClickBehaviour': { + 'search.searchEditor.doubleClickBehaviour': { type: 'string', enum: ['selectWord', 'goToLocation', 'openLocationToSide'], default: 'goToLocation', enumDescriptions: [ - nls.localize('search.searchEditorPreview.doubleClickBehaviour.selectWord', "Double clicking selects the word under the cursor."), - nls.localize('search.searchEditorPreview.doubleClickBehaviour.goToLocation', "Double clicking opens the result in the active editor group."), - nls.localize('search.searchEditorPreview.doubleClickBehaviour.openLocationToSide', "Double clicking opens the result in the editor group to the side, creating one if it does not yet exist."), + nls.localize('search.searchEditor.doubleClickBehaviour.selectWord', "Double clicking selects the word under the cursor."), + nls.localize('search.searchEditor.doubleClickBehaviour.goToLocation', "Double clicking opens the result in the active editor group."), + nls.localize('search.searchEditor.doubleClickBehaviour.openLocationToSide', "Double clicking opens the result in the editor group to the side, creating one if it does not yet exist."), ], - markdownDescription: nls.localize('search.searchEditorPreview.doubleClickBehaviour', "Configure effect of double clicking a result in a Search Editor.\n\n `#search.enableSearchEditorPreview#` must be enabled for this setting to have an effect.") + markdownDescription: nls.localize('search.searchEditor.doubleClickBehaviour', "Configure effect of double clicking a result in a search editor.") }, 'search.sortOrder': { 'type': 'string', diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index c730907976d..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); } @@ -283,7 +283,7 @@ export class RefreshAction extends Action { run(): Promise { const searchView = getSearchView(this.viewsService); if (searchView) { - searchView.onQueryChanged(false); + searchView.triggerQueryChange({ preserveFocus: false }); } return Promise.resolve(); @@ -488,12 +488,19 @@ export class FocusNextSearchResultAction extends Action { static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result"); constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService + @IViewsService private readonly viewsService: IViewsService, + @IEditorService private readonly editorService: IEditorService, ) { super(id, label); } - run(): Promise { + async run(): Promise { + 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.activeEditorPane as SearchEditor).focusNextResult(); + } + return openSearchView(this.viewsService).then(searchView => { if (searchView) { searchView.selectNextMatch(); @@ -507,12 +514,19 @@ export class FocusPreviousSearchResultAction extends Action { static readonly LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus Previous Search Result"); constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService + @IViewsService private readonly viewsService: IViewsService, + @IEditorService private readonly editorService: IEditorService, ) { super(id, label); } - run(): Promise { + async run(): Promise { + 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.activeEditorPane as SearchEditor).focusPreviousResult(); + } + return openSearchView(this.viewsService).then(searchView => { if (searchView) { searchView.selectPreviousMatch(); @@ -697,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; } @@ -721,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; } @@ -741,7 +755,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { private hasToOpenFile(): boolean { const activeEditor = this.editorService.activeEditor; - const file = activeEditor ? activeEditor.getResource() : undefined; + const file = activeEditor ? activeEditor.resource : undefined; if (file) { return file.toString() === this.element.parent().resource.toString(); } diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 068a0ad55ac..0a3fd19f523 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { ITreeNode, ITreeRenderer, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; import { IAction } from 'vs/base/common/actions'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -300,7 +300,7 @@ export class MatchRenderer extends Disposable implements ITreeRenderer { +export class SearchAccessibilityProvider implements IListAccessibilityProvider { constructor( private searchModel: SearchModel, @@ -329,10 +329,10 @@ export class SearchAccessibilityProvider implements IAccessibilityProvider; + private triggerQueryDelayer: Delayer; + private pauseSearching = false; + + private treeAccessibilityProvider: SearchAccessibilityProvider; + constructor( options: IViewPaneOptions, @IFileService private readonly fileService: IFileService, @@ -170,31 +177,33 @@ export class SearchView extends ViewPane { @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.container = dom.$('.search-view'); + // globals + this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(this.contextKeyService); + this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(this.contextKeyService); + this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(this.contextKeyService); + this.fileMatchFocused = Constants.FileFocusKey.bindTo(this.contextKeyService); + this.folderMatchFocused = Constants.FolderFocusKey.bindTo(this.contextKeyService); + this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); + this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); + + // scoped this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container)); - const viewletFocused = Constants.SearchViewFocusedKey.bindTo(this.contextKeyService); - viewletFocused.set(true); - - this.instantiationService = this.instantiationService.createChild( - new ServiceCollection([IContextKeyService, this.contextKeyService])); - - this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService); + Constants.SearchViewFocusedKey.bindTo(this.contextKeyService).set(true); this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService); this.inputPatternIncludesFocused = Constants.PatternIncludesFocusedKey.bindTo(this.contextKeyService); this.inputPatternExclusionsFocused = Constants.PatternExcludesFocusedKey.bindTo(this.contextKeyService); - this.firstMatchFocused = Constants.FirstMatchFocusKey.bindTo(contextKeyService); - this.fileMatchOrMatchFocused = Constants.FileMatchOrMatchFocusKey.bindTo(contextKeyService); - this.fileMatchOrFolderMatchFocus = Constants.FileMatchOrFolderMatchFocusKey.bindTo(contextKeyService); - this.fileMatchOrFolderMatchWithResourceFocus = Constants.FileMatchOrFolderMatchWithResourceFocusKey.bindTo(contextKeyService); - this.fileMatchFocused = Constants.FileFocusKey.bindTo(contextKeyService); - this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService); - this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); - this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); + + this.instantiationService = this.instantiationService.createChild( + new ServiceCollection([IContextKeyService, this.contextKeyService])); this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('search.sortOrder')) { @@ -212,8 +221,8 @@ export class SearchView extends ViewPane { this.memento = new Memento(this.id, storageService); this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE); - this._register(this.fileService.onFileChanges(e => this.onFilesChanged(e))); - this._register(this.textFileService.untitled.onDidDisposeModel(e => this.onUntitledDidDispose(e))); + this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e))); + this._register(this.textFileService.untitled.onDidDispose(model => this.onUntitledDidDispose(model.resource))); this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState())); this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory())); @@ -221,23 +230,21 @@ export class SearchView extends ViewPane { this.addToSearchHistoryDelayer = this._register(new Delayer(2000)); this.toggleCollapseStateDelayer = this._register(new Delayer(100)); + this.triggerQueryDelayer = this._register(new Delayer(0)); const collapseDeepestExpandedLevelAction = this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL); const expandAllAction = this.instantiationService.createInstance(ExpandAllAction, ExpandAllAction.ID, ExpandAllAction.LABEL); this.actions = [ this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), + this._register(this.instantiationService.createInstance(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL)) ]; - if (this.searchConfig.enableSearchEditorPreview) { - this.actions.push( - this._register(this.instantiationService.createInstance(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL)) - ); - } - 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 { @@ -315,7 +322,7 @@ export class SearchView extends ViewPane { this.inputPatternIncludes.setValue(patternIncludes); - this.inputPatternIncludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType)); + this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod })); this.inputPatternIncludes.onCancel(() => this.cancelSearch(false)); this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused); @@ -331,9 +338,9 @@ export class SearchView extends ViewPane { this.inputPatternExcludes.setValue(patternExclusions); this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles); - this.inputPatternExcludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType)); + this.inputPatternExcludes.onSubmit(triggeredOnType => this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod })); this.inputPatternExcludes.onCancel(() => this.cancelSearch(false)); - this.inputPatternExcludes.onChangeIgnoreBox(() => this.onQueryChanged(true)); + this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerQueryChange()); this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused); this.messagesElement = dom.append(this.container, $('.messages')); @@ -436,9 +443,9 @@ export class SearchView extends ViewPane { this.searchWidget.toggleReplace(true); } - this._register(this.searchWidget.onSearchSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType))); + this._register(this.searchWidget.onSearchSubmit(options => this.triggerQueryChange(options))); this._register(this.searchWidget.onSearchCancel(({ focus }) => this.cancelSearch(focus))); - this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.onQueryChanged(true))); + this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.triggerQueryChange())); this._register(this.searchWidget.onDidHeightChange(() => this.reLayout())); @@ -526,7 +533,7 @@ export class SearchView extends ViewPane { } } - private createResultIterator(collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator> { + private createResultIterator(collapseResults: ISearchConfigurationProperties['collapseResults']): Iterable> { const folderMatches = this.searchResult.folderMatches() .filter(fm => !fm.isEmpty()) .sort(searchMatchComparer); @@ -535,20 +542,17 @@ export class SearchView extends ViewPane { return this.createFolderIterator(folderMatches[0], collapseResults); } - const foldersIt = Iterator.fromArray(folderMatches); - return Iterator.map(foldersIt, folderMatch => { + return Iterable.map(folderMatches, folderMatch => { const children = this.createFolderIterator(folderMatch, collapseResults); return >{ element: folderMatch, children }; }); } - private createFolderIterator(folderMatch: FolderMatch, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator> { + private createFolderIterator(folderMatch: FolderMatch, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterable> { const sortOrder = this.searchConfig.sortOrder; - const filesIt = Iterator.fromArray( - folderMatch.matches() - .sort((a, b) => searchMatchComparer(a, b, sortOrder))); + const matches = folderMatch.matches().sort((a, b) => searchMatchComparer(a, b, sortOrder)); - return Iterator.map(filesIt, fileMatch => { + return Iterable.map(matches, fileMatch => { const children = this.createFileIterator(fileMatch); let nodeExists = true; @@ -561,14 +565,12 @@ export class SearchView extends ViewPane { }); } - private createFileIterator(fileMatch: FileMatch): Iterator> { - const matchesIt = Iterator.from( - fileMatch.matches() - .sort(searchMatchComparer)); - return Iterator.map(matchesIt, r => (>{ element: r })); + private createFileIterator(fileMatch: FileMatch): Iterable> { + const matches = fileMatch.matches().sort(searchMatchComparer); + return Iterable.map(matches, r => (>{ element: r })); } - private createIterator(match: FolderMatch | FileMatch | SearchResult, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator> { + private createIterator(match: FolderMatch | FileMatch | SearchResult, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterable> { return match instanceof SearchResult ? this.createResultIterator(collapseResults) : match instanceof FolderMatch ? this.createFolderIterator(match, collapseResults) : this.createFileIterator(match); @@ -714,7 +716,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: { @@ -817,6 +819,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); } } } @@ -846,6 +850,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); } } } @@ -856,11 +862,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) { @@ -869,9 +875,15 @@ export class SearchView extends ViewPane { if (this.searchWidget.searchInput.getRegex()) { selectedText = strings.escapeRegExpCharacters(selectedText); } - this.searchWidget.setValue(selectedText, true); + + if (allowSearchOnType && !this.viewModel.searchResult.hasRemovedResults) { + this.searchWidget.setValue(selectedText); + } else { + this.pauseSearching = true; + this.searchWidget.setValue(selectedText); + this.pauseSearching = false; + } updatedText = true; - if (this.searchConfig.searchOnType) { this.onQueryChanged(false); } } } @@ -1043,26 +1055,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; } @@ -1071,7 +1083,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); } @@ -1099,17 +1111,17 @@ export class SearchView extends ViewPane { toggleCaseSensitive(): void { this.searchWidget.searchInput.setCaseSensitive(!this.searchWidget.searchInput.getCaseSensitive()); - this.onQueryChanged(true); + this.triggerQueryChange(); } toggleWholeWords(): void { this.searchWidget.searchInput.setWholeWords(!this.searchWidget.searchInput.getWholeWords()); - this.onQueryChanged(true); + this.triggerQueryChange(); } toggleRegex(): void { this.searchWidget.searchInput.setRegex(!this.searchWidget.searchInput.getRegex()); - this.onQueryChanged(true); + this.triggerQueryChange(); } setSearchParameters(args: IFindInFilesArgs = {}): void { @@ -1139,7 +1151,7 @@ export class SearchView extends ViewPane { } } if (typeof args.triggerSearch === 'boolean' && args.triggerSearch) { - this.onQueryChanged(true); + this.triggerQueryChange(); } } @@ -1170,7 +1182,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); } } @@ -1228,7 +1240,17 @@ export class SearchView extends ViewPane { this.searchWidget.focus(false); } - onQueryChanged(preserveFocus: boolean, triggeredOnType = false): void { + triggerQueryChange(_options?: { preserveFocus?: boolean, triggeredOnType?: boolean, delay?: number }) { + const options = { preserveFocus: true, triggeredOnType: false, delay: 0, ..._options }; + + if (!this.pauseSearching) { + this.triggerQueryDelayer.trigger(() => { + this._onQueryChanged(options.preserveFocus, options.triggeredOnType); + }, options.delay); + } + } + + private _onQueryChanged(preserveFocus: boolean, triggeredOnType = false): void { if (!this.searchWidget.searchInput.inputBox.isInputValid()) { return; } @@ -1330,7 +1352,7 @@ export class SearchView extends ViewPane { this.inputPatternIncludes.onSearchSubmit(); }); - this.viewModel.cancelSearch(); + this.viewModel.cancelSearch(true); this.currentSearchQ = this.currentSearchQ .then(() => this.doSearch(query, excludePatternText, includePatternText, triggeredOnType)) @@ -1375,6 +1397,10 @@ export class SearchView extends ViewPane { this.updateActions(); const hasResults = !this.viewModel.searchResult.isEmpty(); + if (completed?.exit === SearchCompletionExitCode.NewSearchStarted) { + return; + } + if (completed && completed.limitHit) { this.searchWidget.searchInput.showMessage({ content: nls.localize('searchMaxResultsWarning', "The result set only contains a subset of all matches. Please be more specific in your search to narrow down the results."), @@ -1409,7 +1435,7 @@ export class SearchView extends ViewPane { const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again"))); this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); - this.onQueryChanged(false); + this.triggerQueryChange({ preserveFocus: false }); })); } else if (hasIncludes || hasExcludes) { const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files"))); @@ -1419,7 +1445,7 @@ export class SearchView extends ViewPane { this.inputPatternExcludes.setValue(''); this.inputPatternIncludes.setValue(''); - this.onQueryChanged(false); + this.triggerQueryChange({ preserveFocus: false }); })); } else { const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings"))); @@ -1515,7 +1541,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) : @@ -1540,23 +1566,19 @@ export class SearchView extends ViewPane { resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled"); } - if (this.searchConfig.enableSearchEditorPreview) { - dom.append(messageEl, $('span', undefined, resultMsg + ' - ')); - const span = dom.append(messageEl, $('span')); - const openInEditorLink = dom.append(span, $('a.pointer.prominent', undefined, nls.localize('openInEditor.message', "Open in editor"))); + dom.append(messageEl, $('span', undefined, resultMsg + ' - ')); + const span = dom.append(messageEl, $('span')); + const openInEditorLink = dom.append(span, $('a.pointer.prominent', undefined, nls.localize('openInEditor.message', "Open in editor"))); - openInEditorLink.title = appendKeyBindingLabel( - nls.localize('openInEditor.tooltip', "Copy current search results to an editor"), - this.keybindingService.lookupKeybinding(Constants.OpenInEditorCommandId), this.keybindingService); + openInEditorLink.title = appendKeyBindingLabel( + nls.localize('openInEditor.tooltip', "Copy current search results to an editor"), + this.keybindingService.lookupKeybinding(Constants.OpenInEditorCommandId), this.keybindingService); - this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { - dom.EventHelper.stop(e, false); - this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue()); - })); + this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { + dom.EventHelper.stop(e, false); + this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue()); + })); - } else { - dom.append(messageEl, $('span', undefined, resultMsg)); - } this.reLayout(); } else if (!msgWasHidden) { dom.hide(this.messagesElement); @@ -1584,6 +1606,7 @@ export class SearchView extends ViewPane { const openFolderLink = dom.append(textEl, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder"))); + const actionRunner = new ActionRunner(); this.messageDisposables.push(dom.addDisposableListener(openFolderLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); @@ -1591,7 +1614,7 @@ export class SearchView extends ViewPane { this.instantiationService.createInstance(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL) : this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL); - this.actionRunner!.run(action).then(() => { + actionRunner.run(action).then(() => { action.dispose(); }, err => { action.dispose(); @@ -1832,7 +1855,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}; }`); @@ -1868,9 +1891,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 3f8d572d78a..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); } @@ -121,12 +111,11 @@ export class SearchWidget extends Widget { private replaceActive: IContextKey; private replaceActionBar!: ActionBar; private _replaceHistoryDelayer: Delayer; - private _searchDelayer: Delayer; private ignoreGlobalFindBufferOnNextFocus = false; private previousGlobalFindBufferValue: string | null = null; - private _onSearchSubmit = this._register(new Emitter()); - readonly onSearchSubmit: Event = this._onSearchSubmit.event; + private _onSearchSubmit = this._register(new Emitter<{ triggeredOnType: boolean, delay: number }>()); + readonly onSearchSubmit: Event<{ triggeredOnType: boolean, delay: number }> = this._onSearchSubmit.event; private _onSearchCancel = this._register(new Emitter<{ focus: boolean }>()); readonly onSearchCancel: Event<{ focus: boolean }> = this._onSearchCancel.event; @@ -155,7 +144,6 @@ export class SearchWidget extends Widget { private readonly _onDidToggleContext = new Emitter(); readonly onDidToggleContext: Event = this._onDidToggleContext.event; - private temporarilySkipSearchOnChange = false; private showContextCheckbox!: Checkbox; private contextLinesInput!: InputBox; @@ -177,7 +165,6 @@ export class SearchWidget extends Widget { this._replaceHistoryDelayer = new Delayer(500); - this._searchDelayer = this._register(new Delayer(this.searchConfiguration.searchOnTypeDebouncePeriod)); this.render(container, options); this.configurationService.onDidChangeConfiguration(e => { @@ -420,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 }); @@ -447,10 +433,8 @@ export class SearchWidget extends Widget { this._onReplaceToggled.fire(); } - setValue(value: string, skipSearchOnChange: boolean) { - this.temporarilySkipSearchOnChange = skipSearchOnChange; + setValue(value: string) { this.searchInput.setValue(value); - this.temporarilySkipSearchOnChange = false; } setReplaceAllActionState(enabled: boolean): void { @@ -492,12 +476,10 @@ export class SearchWidget extends Widget { this.setReplaceAllActionState(false); if (this.searchConfiguration.searchOnType) { - if (!this.temporarilySkipSearchOnChange) { - this._onSearchCancel.fire({ focus: false }); - if (this.searchInput.getRegex()) { - try { - const regex = new RegExp(this.searchInput.getValue(), 'ug'); - const matchienessHeuristic = ` + if (this.searchInput.getRegex()) { + try { + const regex = new RegExp(this.searchInput.getValue(), 'ug'); + const matchienessHeuristic = ` ~!@#$%^&*()_+ \`1234567890-= qwertyuiop[]\\ @@ -507,18 +489,17 @@ export class SearchWidget extends Widget { zxcvbnm,./ ZXCVBNM<>? `.match(regex)?.length ?? 0; - const delayMultiplier = - matchienessHeuristic < 50 ? 1 : - matchienessHeuristic < 100 ? 5 : // expressions like `.` or `\w` - 10; // only things matching empty string + const delayMultiplier = + matchienessHeuristic < 50 ? 1 : + matchienessHeuristic < 100 ? 5 : // expressions like `.` or `\w` + 10; // only things matching empty string - this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod * delayMultiplier); - } catch { - // pass - } - } else { - this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod); + this.submitSearch(true, this.searchConfiguration.searchOnTypeDebouncePeriod * delayMultiplier); + } catch { + // pass } + } else { + this.submitSearch(true, this.searchConfiguration.searchOnTypeDebouncePeriod); } } } @@ -628,7 +609,7 @@ export class SearchWidget extends Widget { } } - private submitSearch(triggeredOnType = false): void { + private submitSearch(triggeredOnType = false, delay: number = 0): void { this.searchInput.validate(); if (!this.searchInput.inputBox.isInputValid()) { return; @@ -639,13 +620,20 @@ export class SearchWidget extends Widget { if (value && useGlobalFindBuffer) { this.clipboardServce.writeFindText(value); } - this._onSearchSubmit.fire(triggeredOnType); + 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(); @@ -668,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..ae144c87db4 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -0,0 +1,289 @@ +/*--------------------------------------------------------------------------------------------- + * 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}`; + const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length; + + // Score by symbol label if searching + let symbolScore: number | undefined = undefined; + let symbolMatches: IMatch[] | undefined = undefined; + let skipContainerQuery = false; + if (symbolQuery.original.length > 0) { + + // First: try to score on the entire query, it is possible that + // the symbol matches perfectly (e.g. searching for "change log" + // can be a match on a markdown symbol "change log"). In that + // case we want to skip the container query altogether. + if (symbolQuery !== query) { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, query, 0, symbolLabelIconOffset); + if (symbolScore) { + skipContainerQuery = true; // since we consumed the query, skip any container matching + } + } + + // Otherwise: score on the symbol query and match on the container later + if (!symbolScore) { + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, 0, symbolLabelIconOffset); + 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 (!skipContainerQuery && 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, forcePinned: 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, forcePinned?: 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 || options.forcePinned || 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..6227e360e76 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -8,17 +8,17 @@ import * as collections from 'vs/base/common/collections'; import * as glob from 'vs/base/common/glob'; import { untildify } from 'vs/base/common/labels'; import { values } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import * as strings from 'vs/base/common/strings'; -import { URI as uri } from 'vs/base/common/uri'; +import { URI as uri, URI } from 'vs/base/common/uri'; 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, IWorkspaceFolderData, toWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; 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'; /** * One folder to search and a glob expression that should be applied. @@ -82,8 +82,14 @@ export class QueryBuilder { constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IEnvironmentService private readonly environmentService: IEnvironmentService - ) { } + @IRemotePathService private readonly remotePathService: IRemotePathService + ) { + this.remotePathService.userHome.then(userHome => { + this._userHome = userHome; + }); + } + + private _userHome: URI | undefined; text(contentPattern: IPatternInfo, folderResources?: uri[], options: ITextQueryBuilderOptions = {}): ITextQuery { contentPattern = this.getContentPattern(contentPattern, options); @@ -94,7 +100,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 +140,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 +150,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 +172,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 = { @@ -236,7 +243,13 @@ export class QueryBuilder { }; const segments = splitGlobPattern(pattern) - .map(segment => untildify(segment, this.environmentService.userHome)); + .map(segment => { + if (this._userHome) { + return untildify(segment, this._userHome.fsPath); + } + + return segment; + }); const groups = collections.groupBy(segments, segment => isSearchPath(segment) ? 'searchPaths' : 'exprSegments'); @@ -403,7 +416,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 +429,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 +441,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 +449,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 b0c40aa7ca6..d8e5688911c 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange, ITextSearchContext, ITextSearchResult, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange, ITextSearchContext, ITextSearchResult, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -141,6 +141,15 @@ export class Match { return thisMatchPreviewLines.join('\n'); } + rangeInPreview() { + // convert to editor's base 1 positions. + return { + ...this._fullPreviewRange, + startColumn: this._fullPreviewRange.startColumn + 1, + endColumn: this._fullPreviewRange.endColumn + 1 + }; + } + fullPreviewLines(): string[] { return this._fullPreviewLines.slice(this._fullPreviewRange.startLineNumber, this._fullPreviewRange.endLineNumber + 1); } @@ -597,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(); @@ -687,13 +696,15 @@ export class SearchResult extends Disposable { private _folderMatches: FolderMatchWithResource[] = []; private _otherFilesMatch: FolderMatch | null = null; - private _folderMatchesMap: TernarySearchTree = TernarySearchTree.forPaths(); + private _folderMatchesMap: TernarySearchTree = TernarySearchTree.forUris(); private _showHighlights: boolean = false; private _query: ITextQuery | null = null; private _rangeHighlightDecorations: RangeHighlightDecorations; private disposePastResults: () => void = () => { }; + private _hasRemovedResults = false; + constructor( private _searchModel: SearchModel, @IReplaceService private readonly replaceService: IReplaceService, @@ -705,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 { @@ -716,10 +737,11 @@ 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(); + this._folderMatchesMap = TernarySearchTree.forUris(); if (!query) { return; @@ -729,14 +751,14 @@ export class SearchResult extends Disposable { .map(fq => fq.folder) .map((resource, index) => this.createFolderMatchWithResource(resource, resource.toString(), index, query)); - this._folderMatches.forEach(fm => this._folderMatchesMap.set(fm.resource.toString(), fm)); + this._folderMatches.forEach(fm => this._folderMatchesMap.set(fm.resource, fm)); this._otherFilesMatch = this.createOtherFilesFolderMatch('otherFiles', this._folderMatches.length + 1, query); this._query = query; } private onModelAdded(model: ITextModel): void { - const folderMatch = this._folderMatchesMap.findSubstr(model.uri.toString()); + const folderMatch = this._folderMatchesMap.findSubstr(model.uri); if (folderMatch) { folderMatch.bindModel(model); } @@ -902,7 +924,7 @@ export class SearchResult extends Disposable { } private getFolderMatch(resource: URI): FolderMatch { - const folderMatch = this._folderMatchesMap.findSubstr(resource.toString()); + const folderMatch = this._folderMatchesMap.findSubstr(resource); return folderMatch ? folderMatch : this._otherFilesMatch!; } @@ -941,7 +963,7 @@ export class SearchResult extends Disposable { private disposeMatches(): void { this.folderMatches().forEach(folderMatch => folderMatch.dispose()); this._folderMatches = []; - this._folderMatchesMap = TernarySearchTree.forPaths(); + this._folderMatchesMap = TernarySearchTree.forUris(); this._rangeHighlightDecorations.removeHighlightRange(); } @@ -968,6 +990,7 @@ export class SearchModel extends Disposable { readonly onReplaceTermChanged: Event = this._onReplaceTermChanged.event; private currentCancelTokenSource: CancellationTokenSource | null = null; + private searchCancelledForNewSearch: boolean = false; constructor( @ISearchService private readonly searchService: ISearchService, @@ -1016,8 +1039,7 @@ export class SearchModel extends Disposable { } search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { - this.cancelSearch(); - + this.cancelSearch(true); this._searchQuery = query; if (!this.searchConfig.searchOnType) { @@ -1114,7 +1136,12 @@ export class SearchModel extends Disposable { private onSearchError(e: any, duration: number): void { if (errors.isPromiseCanceledError(e)) { - this.onSearchCompleted(null, duration); + this.onSearchCompleted( + this.searchCancelledForNewSearch + ? { exit: SearchCompletionExitCode.NewSearchStarted, results: [] } + : null, + duration); + this.searchCancelledForNewSearch = false; } } @@ -1133,8 +1160,9 @@ export class SearchModel extends Disposable { return this.configurationService.getValue('search'); } - cancelSearch(): boolean { + cancelSearch(cancelledForNewSearch = false): boolean { if (this.currentCancelTokenSource) { + this.searchCancelledForNewSearch = cancelledForNewSearch; this.currentCancelTokenSource.cancel(); return true; } 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..cc8f788f891 100644 --- a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts @@ -5,16 +5,18 @@ import * as assert from 'assert'; import { IExpression } from 'vs/base/common/glob'; import { join } from 'vs/base/common/path'; +import { isWindows } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; 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 { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -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 { IWorkspaceContextService, toWorkspaceFolder, toWorkspaceFolders, Workspace } 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 { isWindows } from 'vs/base/common/platform'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; +import { IFileQuery, IFolderQuery, IPatternInfo, ITextQuery, QueryType } from 'vs/workbench/services/search/common/search'; +import { TestRemotePathService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; @@ -25,6 +27,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; @@ -47,6 +50,7 @@ suite('QueryBuilder', () => { instantiationService.stub(IWorkspaceContextService, mockContextService); instantiationService.stub(IEnvironmentService, TestEnvironmentService); + instantiationService.stub(IRemotePathService, new TestRemotePathService(TestEnvironmentService)); queryBuilder = instantiationService.createInstance(QueryBuilder); }); @@ -89,7 +93,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 +369,7 @@ suite('QueryBuilder', () => { const content = 'content'; assertEqualQueries( queryBuilder.file( - undefined, + [], { filePattern: ` ${content} ` } ), { @@ -902,10 +909,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..bc60138cbbc 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,11 @@ 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'; +import { TestRemotePathService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; @@ -25,7 +27,7 @@ suite('QueryBuilder', () => { let mockContextService: TestContextService; let mockWorkspace: Workspace; - setup(() => { + setup(async () => { instantiationService = new TestInstantiationService(); mockConfigService = new TestConfigurationService(); @@ -39,8 +41,10 @@ suite('QueryBuilder', () => { instantiationService.stub(IWorkspaceContextService, mockContextService); instantiationService.stub(IEnvironmentService, TestEnvironmentService); + instantiationService.stub(IRemotePathService, new TestRemotePathService(TestEnvironmentService)); queryBuilder = instantiationService.createInstance(QueryBuilder); + await new Promise(resolve => setTimeout(resolve, 5)); // Wait for RemotePathService.userHome to resolve }); suite('parseSearchPaths', () => { @@ -62,13 +66,13 @@ suite('QueryBuilder', () => { [ '~/foo/bar', { - searchPaths: [{ searchPath: getUri(userHome, '/foo/bar') }] + searchPaths: [{ searchPath: getUri(userHome.fsPath, '/foo/bar') }] } ], [ '~/foo/bar, a', { - searchPaths: [{ searchPath: getUri(userHome, '/foo/bar') }], + searchPaths: [{ searchPath: getUri(userHome.fsPath, '/foo/bar') }], pattern: patternsToIExpression(...globalGlob('a')) } ], diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts index f9e5cec7945..39c264c57c9 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -7,15 +7,24 @@ 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'; -export const EnableSearchEditorPreview = new RawContextKey('previewSearchEditor', false); export const InSearchEditor = new RawContextKey('inSearchEditor', false); export const SearchEditorScheme = 'search-editor'; +export const SearchEditorBodyScheme = 'search-editor-body'; export const SearchEditorFindMatchClass = 'seaarchEditorFindMatch'; + +export const SearchEditorID = 'workbench.editor.searchEditor'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 1884a02ec44..746029df38e 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -7,10 +7,11 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as objects from 'vs/base/common/objects'; import { endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; import { localize } from 'vs/nls'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -21,15 +22,15 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry, ActiveEditorContext } from 'vs/workbench/common/editor'; 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 } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; -import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { modifySearchEditorContextLinesCommand, OpenResultsInEditorAction, OpenSearchEditorAction, OpenSearchEditorToSideAction, RerunSearchEditorSearchAction, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { getOrMakeSearchEditorInput, SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { parseSavedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; +import { Range } from 'vs/editor/common/core/range'; //#region Editor Descriptior Registry.as(EditorExtensions.Editors).registerEditor( @@ -51,42 +52,33 @@ class SearchEditorContribution implements IWorkbenchContribution { @IInstantiationService protected readonly instantiationService: IInstantiationService, @ITelemetryService protected readonly telemetryService: ITelemetryService, @IContextKeyService protected readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, ) { - const enableSearchEditorPreview = SearchEditorConstants.EnableSearchEditorPreview.bindTo(this.contextKeyService); - enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); - configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('search.previewSearchEditor')) { - enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); - } - }); + this.editorService.overrideOpenEditor({ + open: (editor, options, group) => { + const resource = editor.resource; + if (!resource) { return undefined; } - this.editorService.overrideOpenEditor((editor, options, group) => { - const resource = editor.getResource(); - if (!resource || - !(endsWith(resource.path, '.code-search') || resource.scheme === SearchEditorConstants.SearchEditorScheme) || - !(editor instanceof FileEditorInput || (resource.scheme === SearchEditorConstants.SearchEditorScheme))) { - return undefined; - } + if (!endsWith(resource.path, '.code-search')) { + return undefined; + } - if (group.isOpened(editor)) { - return undefined; - } + if (group.isOpened(editor) && editor instanceof SearchEditorInput) { + return undefined; + } - if (endsWith(resource.path, '.code-search')) { this.telemetryService.publicLog2('searchEditor/openSavedSearchEditor'); + + return { + override: (async () => { + const { config } = await instantiationService.invokeFunction(parseSavedSearchEditor, resource); + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { backingUri: resource, config }); + return editorService.openEditor(input, { ...options, ignoreOverrides: true }, group); + })() + }; } - - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { uri: resource }); - const opened = editorService.openEditor(input, { ...options, pinned: resource.scheme === SearchEditorConstants.SearchEditorScheme, ignoreOverrides: true }, group); - return { override: Promise.resolve(opened) }; }); } - - private get searchConfig(): ISearchConfigurationProperties { - return this.configurationService.getValue('search'); - } } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); @@ -94,27 +86,30 @@ workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorContrib //#endregion //#region Input Factory +type SerializedSearchEditor = { modelUri: string, dirty: boolean, config: SearchConfiguration, name: string, matchRanges: Range[], backingUri: string }; class SearchEditorInputFactory implements IEditorInputFactory { canSerialize() { return true; } serialize(input: SearchEditorInput) { - let resource = undefined; - if (input.resource.path || input.resource.fragment) { - resource = input.resource.toString(); + let modelUri = undefined; + if (input.modelUri.path || input.modelUri.fragment) { + modelUri = input.modelUri.toString(); } + if (!modelUri) { return undefined; } - const config = input.getConfigSync(); + const config = input.config; const dirty = input.isDirty(); const matchRanges = input.getMatchRanges(); + const backingUri = input.backingUri; - return JSON.stringify({ resource, dirty, config, name: input.getName(), matchRanges }); + return JSON.stringify({ modelUri: modelUri.toString(), dirty, config, name: input.getName(), matchRanges, backingUri: backingUri?.toString() } as SerializedSearchEditor); } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { - const { resource, dirty, config, matchRanges } = JSON.parse(serializedEditorInput); - if (config && (config.query !== undefined)) { - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config, uri: URI.parse(resource) }); + const { modelUri, dirty, config, matchRanges, backingUri } = JSON.parse(serializedEditorInput) as SerializedSearchEditor; + if (config && (config.query !== undefined) && (modelUri !== undefined)) { + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config, modelUri: URI.parse(modelUri), backingUri: backingUri ? URI.parse(backingUri) : undefined }); input.setDirty(dirty); input.setMatchRanges(matchRanges); return input; @@ -158,6 +153,39 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Alt | KeyCode.KEY_L, 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, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, + handler: selectAllSearchEditorMatchesCommand +}); + +CommandsRegistry.registerCommand( + SearchEditorConstants.CleanSearchEditorStateCommandId, + (accessor: ServicesAccessor) => { + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane instanceof SearchEditor) { + activeEditorPane.cleanState(); + } + }); //#endregion //#region Actions @@ -167,12 +195,29 @@ const category = localize('search', "Search Editor"); registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, - ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey, SearchEditorConstants.EnableSearchEditorPreview)), - 'Search Editor: Open Results in Editor', category, - ContextKeyExpr.and(SearchEditorConstants.EnableSearchEditorPreview)); + ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey)), + 'Search Editor: Open Results in Editor', category); registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), - 'Search Editor: Open New Search Editor', category, - ContextKeyExpr.and(SearchEditorConstants.EnableSearchEditorPreview)); + '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: Search Again', category); //#endregion + + +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: RerunSearchEditorSearchAction.ID, + title: RerunSearchEditorSearchAction.LABEL, + icon: { id: 'codicon/refresh' }, + }, + group: 'navigation', + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID)) +}); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 2740d8bf378..a258f9d9b39 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -5,21 +5,29 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { alert } from 'vs/base/browser/ui/aria/aria'; import { Delayer } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +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 { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -28,6 +36,7 @@ import { inputBorder, registerColor, searchEditorFindMatch, searchEditorFindMatc import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorOptions } from 'vs/workbench/common/editor'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; @@ -35,18 +44,12 @@ import { InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; -import { InSearchEditor, SearchEditorFindMatchClass } from 'vs/workbench/contrib/searchEditor/browser/constants'; +import { InSearchEditor, SearchEditorFindMatchClass, SearchEditorID } from 'vs/workbench/contrib/searchEditor/browser/constants'; import type { SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; -import { extractSearchQuery, serializeSearchConfiguration, serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; -import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; +import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; -import { assertIsDefined } from 'vs/base/common/types'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -54,7 +57,7 @@ const FILE_LINE_REGEX = /^(\S.*):$/; type SearchEditorViewState = ICodeEditorViewState & { focused: 'input' | 'editor' }; export class SearchEditor extends BaseTextEditor { - static readonly ID: string = 'workbench.editor.searchEditor'; + static readonly ID: string = SearchEditorID; static readonly SEARCH_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'searchEditorViewState'; @@ -68,7 +71,7 @@ export class SearchEditor extends BaseTextEditor { private toggleQueryDetailsButton!: HTMLElement; private messageBox!: HTMLElement; - private runSearchDelayer = new Delayer(300); + private runSearchDelayer = new Delayer(0); private pauseSearching: boolean = false; private showingIncludesExcludes: boolean = false; private inSearchEditorContextKey: IContextKey; @@ -77,6 +80,7 @@ export class SearchEditor extends BaseTextEditor { private searchHistoryDelayer: Delayer; private messageDisposables: IDisposable[] = []; private container: HTMLElement; + private searchModel: SearchModel; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -107,6 +111,8 @@ export class SearchEditor extends BaseTextEditor { this.inputFocusContextKey = InputBoxFocusedKey.bindTo(scopedContextKeyService); this.searchOperation = this._register(new LongRunningOperation(progressService)); this.searchHistoryDelayer = new Delayer(2000); + + this.searchModel = this._register(this.instantiationService.createInstance(SearchModel)); } createEditor(parent: HTMLElement) { @@ -121,9 +127,9 @@ export class SearchEditor extends BaseTextEditor { this.queryEditorWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.queryEditorContainer, { _hideReplaceToggle: true, showContextToggle: true })); this._register(this.queryEditorWidget.onReplaceToggled(() => this.reLayout())); this._register(this.queryEditorWidget.onDidHeightChange(() => this.reLayout())); - this.queryEditorWidget.onSearchSubmit(() => this.runSearch(true, true)); // onSearchSubmit has an internal delayer, so skip over ours. - this.queryEditorWidget.searchInput.onDidOptionChange(() => this.runSearch(false)); - this.queryEditorWidget.onDidToggleContext(() => this.runSearch(false)); + this.queryEditorWidget.onSearchSubmit(({ delay }) => this.triggerSearch({ delay })); + this.queryEditorWidget.searchInput.onDidOptionChange(() => this.triggerSearch({ resetCursor: false })); + this.queryEditorWidget.onDidToggleContext(() => this.triggerSearch({ resetCursor: false })); // Includes/Excludes Dropdown this.includesExcludesContainer = DOM.append(this.queryEditorContainer, DOM.$('.includes-excludes')); @@ -161,7 +167,7 @@ export class SearchEditor extends BaseTextEditor { this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, { ariaLabel: localize('label.includes', 'Search Include Patterns'), })); - this.inputPatternIncludes.onSubmit(_triggeredOnType => this.runSearch()); + this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 })); // // Excludes const excludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.excludes')); @@ -170,8 +176,8 @@ export class SearchEditor extends BaseTextEditor { this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, { ariaLabel: localize('label.excludes', 'Search Exclude Patterns'), })); - this.inputPatternExcludes.onSubmit(_triggeredOnType => this.runSearch()); - this.inputPatternExcludes.onChangeIgnoreBox(() => this.runSearch()); + this.inputPatternExcludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 })); + this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerSearch()); [this.queryEditorWidget.searchInput, this.inputPatternIncludes, this.inputPatternExcludes].map(input => this._register(attachInputBoxStyler(input, this.themeService, { inputBorder: searchEditorTextInputBorder }))); @@ -180,7 +186,6 @@ export class SearchEditor extends BaseTextEditor { this.messageBox = DOM.append(this.queryEditorContainer, DOM.$('.messages')); } - private toggleRunAgainMessage(show: boolean) { DOM.clearNode(this.messageBox); dispose(this.messageDisposables); @@ -189,7 +194,8 @@ export class SearchEditor extends BaseTextEditor { if (show) { const runAgainLink = DOM.append(this.messageBox, DOM.$('a.pointer.prominent.message', {}, localize('runSearch', "Run Search"))); this.messageDisposables.push(DOM.addDisposableListener(runAgainLink, DOM.EventType.CLICK, async () => { - await this.runSearch(true, true); + await this.triggerSearch(); + this.searchResultEditor.focus(); this.toggleRunAgainMessage(false); })); } @@ -201,7 +207,7 @@ export class SearchEditor extends BaseTextEditor { this.searchResultEditor = super.getControl() as CodeEditorWidget; this.searchResultEditor.onMouseUp(e => { if (e.event.detail === 2) { - const behaviour = this.configurationService.getValue('search').searchEditorPreview.doubleClickBehaviour; + const behaviour = this.configurationService.getValue('search').searchEditor.doubleClickBehaviour; const position = e.target.position; if (position && behaviour !== 'selectWord') { const line = this.searchResultEditor.getModel()?.getLineContent(position.lineNumber) ?? ''; @@ -272,44 +278,154 @@ export class SearchEditor extends BaseTextEditor { toggleWholeWords() { this.queryEditorWidget.searchInput.setWholeWords(!this.queryEditorWidget.searchInput.getWholeWords()); - this.runSearch(false); + this.triggerSearch({ resetCursor: false }); } toggleRegex() { this.queryEditorWidget.searchInput.setRegex(!this.queryEditorWidget.searchInput.getRegex()); - this.runSearch(false); + this.triggerSearch({ resetCursor: false }); } toggleCaseSensitive() { this.queryEditorWidget.searchInput.setCaseSensitive(!this.queryEditorWidget.searchInput.getCaseSensitive()); - this.runSearch(false); + this.triggerSearch({ resetCursor: false }); } toggleContextLines() { this.queryEditorWidget.toggleContextLines(); } + modifyContextLines(increase: boolean) { + this.queryEditorWidget.modifyContextLines(increase); + } + toggleQueryDetails() { this.toggleIncludesExcludes(); } - async runSearch(resetCursor = true, instant = false) { + 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); + } + + private get searchConfig(): ISearchConfigurationProperties { + return this.configurationService.getValue('search'); + } + + private iterateThroughMatches(reverse: boolean) { + const model = this.searchResultEditor.getModel(); + if (!model) { return; } + + const lastLine = model.getLineCount() ?? 1; + const lastColumn = model.getLineLength(lastLine); + + const fallbackStart = reverse ? new Position(lastLine, lastColumn) : new Position(1, 1); + + const currentPosition = this.searchResultEditor.getSelection()?.getStartPosition() ?? fallbackStart; + + const matchRanges = this.getInput()?.getMatchRanges(); + if (!matchRanges) { return; } + + const matchRange = (reverse ? findPrevRange : findNextRange)(matchRanges, currentPosition); + + 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() { + this.iterateThroughMatches(false); + } + + focusPreviousResult() { + this.iterateThroughMatches(true); + } + + focusAllResults() { + this.searchResultEditor + .setSelections((this.getInput()?.getMatchRanges() ?? []).map( + range => new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn))); + this.searchResultEditor.focus(); + } + + async triggerSearch(_options?: { resetCursor?: boolean; delay?: number; }) { + const options = { resetCursor: true, delay: 0, ..._options }; + if (!this.pauseSearching) { await this.runSearchDelayer.trigger(async () => { await this.doRunSearch(); this.toggleRunAgainMessage(false); - if (resetCursor) { - this.searchResultEditor.setSelection(new Range(1, 1, 1, 1)); + if (options.resetCursor) { + this.searchResultEditor.setPosition(new Position(1, 1)); this.searchResultEditor.setScrollPosition({ scrollTop: 0, scrollLeft: 0 }); } - }, instant ? 0 : undefined); + }, options.delay); } } 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(), @@ -321,6 +437,8 @@ export class SearchEditor extends BaseTextEditor { } private async doRunSearch() { + this.searchModel.cancelSearch(true); + const startInput = this.getInput(); this.searchHistoryDelayer.trigger(() => { @@ -344,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: { @@ -367,30 +485,26 @@ export class SearchEditor extends BaseTextEditor { catch (err) { return; } - const searchModel = this.instantiationService.createInstance(SearchModel); + this.searchOperation.start(500); - await searchModel.search(query).finally(() => this.searchOperation.stop()); + await this.searchModel.search(query).finally(() => this.searchOperation.stop()); const input = this.getInput(); if (!input || input !== startInput || JSON.stringify(config) !== JSON.stringify(this.readConfigFromWidget())) { - - searchModel.dispose(); return; } const controller = ReferencesController.get(this.searchResultEditor); controller.closeWidget(false); const labelFormatter = (uri: URI): string => this.labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, false); - const { header, body } = await input.getModels(); + const results = serializeSearchResultForEditor(this.searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter); + const { body } = await input.getModels(); this.modelService.updateModel(body, results.text); - header.setValue(serializeSearchConfiguration(config)); + input.config = config; - input.setDirty(input.resource.scheme !== 'search-editor'); + input.setDirty(!input.isUntitled()); input.setMatchRanges(results.matchRanges); - - searchModel.dispose(); } layout(dimension: DOM.Dimension) { @@ -424,15 +538,14 @@ export class SearchEditor extends BaseTextEditor { await super.setInput(newInput, options, token); - const { body, header } = await newInput.getModels(); + const { body, config } = await newInput.getModels(); this.searchResultEditor.setModel(body); this.pauseSearching = true; - const config = extractSearchQuery(header); this.toggleRunAgainMessage(body.getLineCount() === 1 && body.getValue() === '' && config.query !== ''); - this.queryEditorWidget.setValue(config.query, true); + this.queryEditorWidget.setValue(config.query); this.queryEditorWidget.searchInput.setCaseSensitive(config.caseSensitive); this.queryEditorWidget.searchInput.setRegex(config.regexp); this.queryEditorWidget.searchInput.setWholeWords(config.wholeWord); @@ -443,6 +556,11 @@ export class SearchEditor extends BaseTextEditor { this.toggleIncludesExcludes(config.showIncludesExcludes); this.restoreViewState(); + + if (!options?.preserveFocus) { + this.focus(); + } + this.pauseSearching = false; } @@ -469,7 +587,7 @@ export class SearchEditor extends BaseTextEditor { } private saveViewState() { - const resource = this.getInput()?.resource; + const resource = this.getInput()?.modelUri; if (resource) { this.saveTextEditorViewState(resource); } } @@ -477,24 +595,19 @@ export class SearchEditor extends BaseTextEditor { const control = this.getControl(); const editorViewState = control.saveViewState(); if (!editorViewState) { return null; } - if (resource.toString() !== this.getInput()?.resource.toString()) { return null; } + if (resource.toString() !== this.getInput()?.modelUri.toString()) { return null; } return { ...editorViewState, focused: this.searchResultEditor.hasWidgetFocus() ? 'editor' : 'input' }; } private loadViewState() { - const resource = assertIsDefined(this.input?.getResource()); + const resource = assertIsDefined(this.getInput()?.modelUri); return this.loadTextEditorViewState(resource) as SearchEditorViewState; } private restoreViewState() { const viewState = this.loadViewState(); if (viewState) { this.searchResultEditor.restoreViewState(viewState); } - if (viewState && viewState.focused === 'editor') { - this.searchResultEditor.focus(); - } else { - this.queryEditorWidget.focus(); - } } clearInput() { @@ -517,3 +630,24 @@ registerThemingParticipant((theme, collector) => { }); export const searchEditorTextInputBorder = registerColor('searchEditor.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "Search editor text input box border.")); + +function findNextRange(matchRanges: Range[], currentPosition: Position) { + for (const matchRange of matchRanges) { + if (Position.isBefore(currentPosition, matchRange.getStartPosition())) { + return matchRange; + } + } + return matchRanges[0]; +} + +function findPrevRange(matchRanges: Range[], currentPosition: Position) { + for (let i = matchRanges.length - 1; i >= 0; i--) { + const matchRange = matchRanges[i]; + if (Position.isBefore(matchRange.getStartPosition(), currentPosition)) { + { + return matchRange; + } + } + } + return matchRanges[matchRanges.length - 1]; +} diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index e07b696c2cf..2932ccfe49a 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,23 @@ 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); + } +}; + +export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).focusAllResults(); } }; @@ -61,7 +77,6 @@ export class OpenSearchEditorAction extends Action { static readonly LABEL = localize('search.openNewEditor', "Open New Search Editor"); constructor(id: string, label: string, - @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, label, 'codicon-new-file'); @@ -76,9 +91,23 @@ export class OpenSearchEditorAction extends Action { } async run() { - if (this.configurationService.getValue('search').enableSearchEditorPreview) { - await this.instantiationService.invokeFunction(openNewSearchEditor); - } + await this.instantiationService.invokeFunction(openNewSearchEditor); + } +} + +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); } } @@ -89,7 +118,6 @@ export class OpenResultsInEditorAction extends Action { constructor(id: string, label: string, @IViewsService private viewsService: IViewsService, - @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, label, 'codicon-go-to-file'); @@ -106,48 +134,66 @@ export class OpenResultsInEditorAction extends Action { async run() { const searchView = getSearchView(this.viewsService); - if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { + if (searchView) { await this.instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); } } } +export class RerunSearchEditorSearchAction extends Action { + static readonly ID: string = Constants.RerunSearchEditorSearchCommandId; + static readonly LABEL = localize('search.rerunSearchInEditor', "Search Again"); + + constructor(id: string, label: string, + @IEditorService private readonly editorService: IEditorService, + ) { + super(id, label, 'codicon-refresh'); + } + + async run() { + const input = this.editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (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(); } } telemetryService.publicLog2('searchEditor/openNewSearchEditor'); - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected } }); - const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected }, text: '' }); + const editor = await editorService.openEditor(input, { pinned: true }, toSide ? SIDE_GROUP : ACTIVE_GROUP) as SearchEditor; if (selected && configurationService.getValue('search').searchOnType) { - editor.runSearch(true, true); + editor.triggerSearch(); } }; @@ -168,9 +214,9 @@ export const createEditorFromSearchResult = const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const { text, matchRanges } = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, true); + const { text, matchRanges, config } = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter); - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text }); + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text, config }); await editorService.openEditor(input, { pinned: true }); input.setMatchRanges(matchRanges); }; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 1fab1a89e67..86321677e9e 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -6,7 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import * as network from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; -import { isEqual, joinPath } from 'vs/base/common/resources'; +import { extname, isEqual, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; import { Range } from 'vs/editor/common/core/range'; @@ -14,18 +14,21 @@ import { DefaultEndOfLine, ITextModel, TrackedRangeStickiness } from 'vs/editor/ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { EditorInput, GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { EditorInput, GroupIdentifier, IEditorInput, IMoveResult, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { Memento } from 'vs/workbench/common/memento'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { SearchEditorFindMatchClass, SearchEditorScheme } from 'vs/workbench/contrib/searchEditor/browser/constants'; -import { extractSearchQuery, serializeSearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; +import { defaultSearchConfig, extractSearchQueryFromModel, parseSavedSearchEditor, serializeSearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; +import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { ITextFileSaveOptions, ITextFileService, snapshotToString, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -42,26 +45,40 @@ export type SearchConfiguration = { showIncludesExcludes: boolean, }; +const SEARCH_EDITOR_EXT = '.code-search'; + export class SearchEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; - private dirty: boolean = false; - private readonly contentsModel: Promise; - private readonly headerModel: Promise; - private _cachedContentsModel: ITextModel | undefined; - private _cachedConfig?: SearchConfiguration; + private memento: Memento; - private readonly _onDidChangeContent = new Emitter(); + private dirty: boolean = false; + private model: Promise; + private _cachedModel: ITextModel | undefined; + + private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent: Event = this._onDidChangeContent.event; private oldDecorationsIDs: string[] = []; + private _config: Readonly; + public get config(): Readonly { return this._config; } + public set config(value: Readonly) { + this._config = value; + this.memento.getMemento(StorageScope.WORKSPACE).searchConfig = value; + this._onDidChangeLabel.fire(); + } + + get resource() { + return this.backingUri || this.modelUri; + } + constructor( - public readonly resource: URI, - getModel: () => Promise<{ contentsModel: ITextModel, headerModel: ITextModel }>, + public readonly modelUri: URI, + public readonly backingUri: URI | undefined, + config: Readonly, + getModel: () => Promise, @IModelService private readonly modelService: IModelService, - @IEditorService protected readonly editorService: IEditorService, - @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, @ITextFileService protected readonly textFileService: ITextFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @@ -69,40 +86,30 @@ export class SearchEditorInput extends EditorInput { @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IModeService readonly modeService: IModeService, - @IRemotePathService private readonly remotePathService: IRemotePathService + @IRemotePathService private readonly remotePathService: IRemotePathService, + @IStorageService storageService: IStorageService, ) { super(); - // Dummy model to set file icon - this._register(modelService.createModel('', modeService.create('search-result'), this.resource)); - - const modelLoader = getModel() - .then(({ contentsModel, headerModel }) => { - this._register(contentsModel.onDidChangeContent(() => this._onDidChangeContent.fire())); - this._register(headerModel.onDidChangeContent(() => { - this._cachedConfig = extractSearchQuery(headerModel); - this._onDidChangeContent.fire(); - this._onDidChangeLabel.fire(); - })); - - this._cachedConfig = extractSearchQuery(headerModel); - this._cachedContentsModel = contentsModel; - - this._register(contentsModel); - this._register(headerModel); - this._onDidChangeLabel.fire(); - - return { contentsModel, headerModel }; + this._config = config; + this.model = getModel() + .then(model => { + this._register(model.onDidChangeContent(() => this._onDidChangeContent.fire())); + this._register(model); + this._cachedModel = model; + return model; }); - this.contentsModel = modelLoader.then(({ contentsModel }) => contentsModel); - this.headerModel = modelLoader.then(({ headerModel }) => headerModel); + if (this.modelUri.scheme !== SearchEditorScheme) { + throw Error('SearchEditorInput must be invoked with a SearchEditorScheme uri'); + } + this.memento = new Memento(SearchEditorInput.ID, storageService); + storageService.onWillSaveState(() => this.memento.saveMemento()); const input = this; const workingCopyAdapter = new class implements IWorkingCopy { - readonly resource = input.getResource(); + readonly resource = input.modelUri; get name() { return input.getName(); } readonly capabilities = input.isUntitled() ? WorkingCopyCapabilities.Untitled : 0; readonly onDidChangeDirty = input.onDidChangeDirty; @@ -110,46 +117,41 @@ 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); - } - - getResource() { - return this.resource; + this._register(this.workingCopyService.registerWorkingCopy(workingCopyAdapter)); } async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - if ((await this.headerModel).isDisposed() || (await this.contentsModel).isDisposed()) { return; } + if ((await this.model).isDisposed()) { return; } - if (this.isUntitled()) { - return this.saveAs(group, options); - } else { - await this.textFileService.write(this.resource, await this.serializeForDisk(), options); + if (this.backingUri) { + await this.textFileService.write(this.backingUri, await this.serializeForDisk(), options); this.setDirty(false); return this; + } else { + return this.saveAs(group, options); } } private async serializeForDisk() { - return (await this.headerModel).getValue() + '\n' + (await this.contentsModel).getValue(); + return serializeSearchConfiguration(this.config) + '\n' + (await this.model).getValue(); } async getModels() { - const header = await this.headerModel; - const body = await this.contentsModel; - return { header, body }; + return { config: this.config, body: await this.model }; } async saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { const path = await this.fileDialogService.pickFileToSave(await this.suggestFileName(), options?.availableFileSystems); if (path) { this.telemetryService.publicLog2('searchEditor/saveSearchResults'); - if (await this.textFileService.create(path, await this.serializeForDisk())) { + const toWrite = await this.serializeForDisk(); + if (await this.textFileService.create(path, toWrite, { overwrite: true })) { this.setDirty(false); - if (!isEqual(path, this.resource)) { - const input = this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { uri: path }); + if (!isEqual(path, this.modelUri)) { + const input = this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: this.config, backingUri: path }); input.setMatchRanges(this.getMatchRanges()); return input; } @@ -166,19 +168,15 @@ export class SearchEditorInput extends EditorInput { getName(maxLength = 12): string { const trimToMax = (label: string) => (label.length < maxLength ? label : `${label.slice(0, maxLength - 3)}...`); - if (this.isUntitled()) { - const query = this._cachedConfig?.query?.trim(); - if (query) { - return localize('searchTitle.withQuery', "Search: {0}", trimToMax(query)); - } - return localize('searchTitle', "Search"); + if (this.backingUri) { + return localize('searchTitle.withQuery', "Search: {0}", basename(this.backingUri?.path, SEARCH_EDITOR_EXT)); } - return localize('searchTitle.withQuery', "Search: {0}", basename(this.resource.path, '.code-search')); - } - - getConfigSync() { - return this._cachedConfig; + const query = this.config.query?.trim(); + if (query) { + return localize('searchTitle.withQuery', "Search: {0}", trimToMax(query)); + } + return localize('searchTitle', "Search"); } async resolve() { @@ -215,11 +213,21 @@ export class SearchEditorInput extends EditorInput { } isUntitled() { - return this.resource.scheme === SearchEditorScheme; + return !this.backingUri; + } + + move(group: GroupIdentifier, target: URI): IMoveResult | undefined { + if (this._cachedModel && extname(target) === SEARCH_EDITOR_EXT) { + return { + editor: this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: this.config, text: this._cachedModel.getValue(), backingUri: target }) + }; + } + // Ignore move if editor was renamed to a different file extension + return undefined; } dispose() { - this.modelService.destroyModel(this.resource); + this.modelService.destroyModel(this.modelUri); super.dispose(); } @@ -227,45 +235,51 @@ export class SearchEditorInput extends EditorInput { if (this === other) { return true; } if (other instanceof SearchEditorInput) { - if ( - (other.resource.path && other.resource.path === this.resource.path) || - (other.resource.fragment && other.resource.fragment === this.resource.fragment) - ) { - return true; - } + return !!(other.modelUri.fragment && other.modelUri.fragment === this.modelUri.fragment); + } else if (other instanceof FileEditorInput) { + return other.resource?.toString() === this.backingUri?.toString(); } return false; } - public getMatchRanges(): Range[] { - return (this._cachedContentsModel?.getAllDecorations() ?? []) + getMatchRanges(): Range[] { + return (this._cachedModel?.getAllDecorations() ?? []) .filter(decoration => decoration.options.className === SearchEditorFindMatchClass) + .filter(({ range }) => !(range.startColumn === 1 && range.endColumn === 1)) .map(({ range }) => range); } - public async setMatchRanges(ranges: Range[]) { - this.oldDecorationsIDs = (await this.contentsModel).deltaDecorations(this.oldDecorationsIDs, ranges.map(range => + async setMatchRanges(ranges: Range[]) { + this.oldDecorationsIDs = (await this.model).deltaDecorations(this.oldDecorationsIDs, ranges.map(range => ({ range, options: { className: SearchEditorFindMatchClass, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); } async revert(group: GroupIdentifier, options?: IRevertOptions) { // TODO: this should actually revert the contents. But it needs to set dirty false. + if (this.backingUri) { + const { config, text } = await this.instantiationService.invokeFunction(parseSavedSearchEditor, this.backingUri); + (await this.model).setValue(text); + this.config = config; + } else { + (await this.model).setValue(''); + } super.revert(group, options); this.setDirty(false); - return true; + } + + supportsSplitEditor() { + return false; } private async backup(): Promise { - const content = stringToSnapshot(await this.serializeForDisk()); + const content = stringToSnapshot((await this.model).getValue()); return { content }; } - // Bringing this over from textFileService because it only suggests for untitled scheme. - // In the future I may just use the untitled scheme. I dont get particular benefit from using search-editor... private async suggestFileName(): Promise { - const query = extractSearchQuery(await this.headerModel).query; + const query = extractSearchQueryFromModel(await this.model).query; - const searchFileName = (query.replace(/[^\w \-_]+/g, '_') || 'Search') + '.code-search'; + const searchFileName = (query.replace(/[^\w \-_]+/g, '_') || 'Search') + SEARCH_EDITOR_EXT; const remoteAuthority = this.environmentService.configuration.remoteAuthority; const schemeFilter = remoteAuthority ? network.Schemas.vscodeRemote : network.Schemas.file; @@ -277,21 +291,28 @@ export class SearchEditorInput extends EditorInput { const inputs = new Map(); export const getOrMakeSearchEditorInput = ( accessor: ServicesAccessor, - existingData: - { uri: URI, config?: Partial, text?: never } | - { text: string, uri?: never, config?: never } | - { config: Partial, text?: never, uri?: never } + existingData: ({ config: Partial, backingUri?: URI } & + ({ modelUri: URI, text?: never, } | + { text: string, modelUri?: never, } | + { backingUri: URI, text?: never, modelUri?: never })) ): SearchEditorInput => { - const uri = existingData.uri ?? URI.from({ scheme: SearchEditorScheme, fragment: `${Math.random()}` }); - const instantiationService = accessor.get(IInstantiationService); const modelService = accessor.get(IModelService); - const textFileService = accessor.get(ITextFileService); const backupService = accessor.get(IBackupFileService); const modeService = accessor.get(IModeService); + const storageService = accessor.get(IStorageService); + const configurationService = accessor.get(IConfigurationService); - const existing = inputs.get(uri.toString()); + const reuseOldSettings = configurationService.getValue('search').searchEditor?.experimental?.reusePriorSearchConfiguration; + const priorConfig: SearchConfiguration = reuseOldSettings ? new Memento(SearchEditorInput.ID, storageService).getMemento(StorageScope.WORKSPACE).searchConfig : {}; + const defaultConfig = defaultSearchConfig(); + let config = { ...defaultConfig, ...priorConfig, ...existingData.config }; + + const modelUri = existingData.modelUri ?? URI.from({ scheme: SearchEditorScheme, fragment: `${Math.random()}` }); + + + const existing = inputs.get(modelUri.toString() + existingData.backingUri?.toString()); if (existing) { return existing; } @@ -299,52 +320,29 @@ export const getOrMakeSearchEditorInput = ( const getModel = async () => { let contents: string; - const backup = await backupService.resolve(uri); + const backup = await backupService.resolve(modelUri); if (backup) { // this way of stringifying a TextBufferFactory seems needlessly complicated... contents = snapshotToString(backup.value.create(DefaultEndOfLine.LF).createSnapshot(true)); - } else if (uri.scheme !== SearchEditorScheme) { - contents = (await textFileService.read(uri)).value; - } else if (existingData.text) { + } else if (existingData.text !== undefined) { contents = existingData.text; - } else if (existingData.config) { - contents = serializeSearchConfiguration(existingData.config); + } else if (existingData.backingUri !== undefined) { + const { text } = await instantiationService.invokeFunction(parseSavedSearchEditor, existingData.backingUri); + contents = text; + } else if (config !== undefined) { + contents = ''; } else { throw new Error('no initial contents for search editor'); } - backupService.discardBackup(uri); + backupService.discardBackup(modelUri); - const lines = contents.split(/\r?\n/); - - const headerlines = []; - const bodylines = []; - let inHeader = true; - for (const line of lines) { - if (inHeader) { - headerlines.push(line); - if (line === '') { - inHeader = false; - } - } else { - bodylines.push(line); - } - } - - const contentsModelURI = uri.with({ scheme: 'search-editor-body' }); - const headerModelURI = uri.with({ scheme: 'search-editor-header' }); - const contentsModel = modelService.getModel(contentsModelURI) ?? modelService.createModel('', modeService.create('search-result'), contentsModelURI); - const headerModel = modelService.getModel(headerModelURI) ?? modelService.createModel('', modeService.create('search-result'), headerModelURI); - - contentsModel.setValue(bodylines.join('\n')); - headerModel.setValue(headerlines.join('\n')); - - return { contentsModel, headerModel }; + return modelService.getModel(modelUri) ?? modelService.createModel(contents, modeService.create('search-result'), modelUri); }; - const input = instantiationService.createInstance(SearchEditorInput, uri, getModel); + const input = instantiationService.createInstance(SearchEditorInput, modelUri, existingData.backingUri, config, getModel); - inputs.set(uri.toString(), input); - input.onDispose(() => inputs.delete(uri.toString())); + inputs.set(modelUri.toString(), input); + input.onDispose(() => inputs.delete(modelUri.toString())); return input; }; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index eca8524c25f..2557745c9d1 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -3,16 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/searchEditor'; import { coalesce, flatten } from 'vs/base/common/arrays'; import { repeat } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; +import 'vs/css!./media/searchEditor'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; -import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; -import { ITextQuery } from 'vs/workbench/services/search/common/search'; -import { localize } from 'vs/nls'; import type { ITextModel } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; +import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import type { SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { ITextQuery } from 'vs/workbench/services/search/common/search'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; // Using \r\n on Windows inserts an extra newline between results. const lineDelimiter = '\n'; @@ -22,11 +24,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,15 +35,15 @@ 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?$/, ''); const rangeOnThisLine = ({ start, end }: { start?: number; end?: number; }) => new Range(1, (start ?? 1) + prefixOffset, 1, (end ?? sourceLine.length + 1) + prefixOffset); - const matchRange = match.range(); + const matchRange = match.rangeInPreview(); const matchIsSingleLine = matchRange.startLineNumber === matchRange.endLineNumber; let lineRange; @@ -60,9 +61,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 +85,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; } @@ -105,17 +106,17 @@ function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: return { text, matchRanges }; } -const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string, contextLines: number): string[] => { - return serializeSearchConfiguration({ - query: pattern?.contentPattern.pattern, - regexp: pattern?.contentPattern.isRegExp, - caseSensitive: pattern?.contentPattern.isCaseSensitive, - wholeWord: pattern?.contentPattern.isWordMatch, +const contentPatternToSearchConfiguration = (pattern: ITextQuery, includes: string, excludes: string, contextLines: number): SearchConfiguration => { + return { + query: pattern.contentPattern.pattern, + regexp: !!pattern.contentPattern.isRegExp, + caseSensitive: !!pattern.contentPattern.isCaseSensitive, + wholeWord: !!pattern.contentPattern.isWordMatch, excludes, includes, showIncludesExcludes: !!(includes || excludes || pattern?.userDisabledExcludesAndIgnoreFiles), - useIgnores: pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? undefined : !pattern.userDisabledExcludesAndIgnoreFiles, + useIgnores: !!(pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? undefined : !pattern.userDisabledExcludesAndIgnoreFiles), contextLines, - }).split(lineDelimiter); + }; }; export const serializeSearchConfiguration = (config: Partial): string => { @@ -140,23 +141,24 @@ export const serializeSearchConfiguration = (config: Partial + extractSearchQueryFromLines(model.getValueInRange(new Range(1, 1, 6, 1)).split(lineDelimiter)); -export const extractSearchQuery = (model: ITextModel | string): SearchConfiguration => { - const header = (typeof model === 'string') - ? model - : model.getValueInRange(new Range(1, 1, 6, 1)).split(lineDelimiter); +export const defaultSearchConfig = (): SearchConfiguration => ({ + query: '', + includes: '', + excludes: '', + regexp: false, + caseSensitive: false, + useIgnores: true, + wholeWord: false, + contextLines: 0, + showIncludesExcludes: false, +}); - const query: SearchConfiguration = { - query: '', - includes: '', - excludes: '', - regexp: false, - caseSensitive: false, - useIgnores: true, - wholeWord: false, - contextLines: 0, - showIncludesExcludes: false, - }; +export const extractSearchQueryFromLines = (lines: string[]): SearchConfiguration => { + + const query = defaultSearchConfig(); const unescapeNewlines = (str: string) => { let out = ''; @@ -182,7 +184,7 @@ export const extractSearchQuery = (model: ITextModel | string): SearchConfigurat }; const parseYML = /^# ([^:]*): (.*)$/; - for (const line of header) { + for (const line of lines) { const parsed = parseYML.exec(line); if (!parsed) { continue; } const [, key, value] = parsed; @@ -206,14 +208,16 @@ export const extractSearchQuery = (model: ITextModel | string): SearchConfigurat }; export const serializeSearchResultForEditor = - (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string, includeHeader: boolean): { matchRanges: Range[], text: string } => { - const header = includeHeader - ? contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines) - : []; + (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string): { matchRanges: Range[], text: string, config: Partial } => { + if (!searchResult.query) { throw Error('Internal Error: Expected query, got null'); } + const config = contentPatternToSearchConfiguration(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines); + + const filecount = searchResult.fileCount() > 1 ? localize('numFiles', "{0} files", searchResult.fileCount()) : localize('oneFile', "1 file"); + const resultcount = searchResult.count() > 1 ? localize('numResults', "{0} results", searchResult.count()) : localize('oneResult', "1 result"); const info = [ searchResult.count() - ? localize('resultCount', "{0} results in {1} files", searchResult.count(), searchResult.fileCount()) + ? `${resultcount} - ${filecount}` : localize('noResults', "No Results"), '']; @@ -226,7 +230,8 @@ export const serializeSearchResultForEditor = return { matchRanges: allResults.matchRanges.map(translateRangeLines(info.length)), - text: header.concat(info).concat(allResults.text).join(lineDelimiter) + text: info.concat(allResults.text).join(lineDelimiter), + config }; }; @@ -242,3 +247,26 @@ const flattenSearchResultSerializations = (serializations: SearchResultSerializa return { text, matchRanges }; }; + +export const parseSavedSearchEditor = async (accessor: ServicesAccessor, resource: URI) => { + const textFileService = accessor.get(ITextFileService); + + const text = (await textFileService.read(resource)).value; + + const headerlines = []; + const bodylines = []; + + let inHeader = true; + for (const line of text.split(/\r?\n/g)) { + if (inHeader) { + headerlines.push(line); + if (line === '') { + inHeader = false; + } + } else { + bodylines.push(line); + } + } + + return { config: extractSearchQueryFromLines(headerlines), text: bodylines.join('\n') }; +}; diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index e2073066f37..d2e10650d03 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'; @@ -24,8 +23,8 @@ import { joinPath, basename } from 'vs/base/common/resources'; const id = 'workbench.action.openSnippets'; namespace ISnippetPick { - export function is(thing: object): thing is ISnippetPick { - return thing && URI.isUri((thing).filepath); + export function is(thing: object | undefined): thing is ISnippetPick { + return !!thing && URI.isUri((thing).filepath); } } @@ -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..6c197f94cac 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Registry } from 'vs/platform/registry/common/platform'; import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import * as nls from 'vs/nls'; @@ -17,7 +17,7 @@ export interface ISnippetsService { _serviceBrand: undefined; - getSnippetFiles(): Promise; + getSnippetFiles(): Promise>; getSnippets(languageId: LanguageId): Promise; @@ -25,6 +25,25 @@ export interface ISnippetsService { } const languageScopeSchemaId = 'vscode://schemas/snippets'; + +const snippetSchemaProperties: IJSONSchemaMap = { + prefix: { + description: nls.localize('snippetSchema.json.prefix', 'The prefix to used when selecting the snippet in intellisense'), + type: ['string', 'array'] + }, + body: { + markdownDescription: nls.localize('snippetSchema.json.body', 'The snippet content. Use `$1`, `${1:defaultText}` to define cursor positions, use `$0` for the final cursor position. Insert variable values with `${varName}` and `${varName:defaultText}`, e.g. `This is file: $TM_FILENAME`.'), + type: ['string', 'array'], + items: { + type: 'string' + } + }, + description: { + description: nls.localize('snippetSchema.json.description', 'The snippet description.'), + type: ['string', 'array'] + } +}; + const languageScopeSchema: IJSONSchema = { id: languageScopeSchemaId, allowComments: true, @@ -38,23 +57,7 @@ const languageScopeSchema: IJSONSchema = { additionalProperties: { type: 'object', required: ['prefix', 'body'], - properties: { - prefix: { - description: nls.localize('snippetSchema.json.prefix', 'The prefix to used when selecting the snippet in intellisense'), - type: ['string', 'array'] - }, - body: { - description: nls.localize('snippetSchema.json.body', 'The snippet content. Use \'$1\', \'${1:defaultText}\' to define cursor positions, use \'$0\' for the final cursor position. Insert variable values with \'${varName}\' and \'${varName:defaultText}\', e.g. \'This is file: $TM_FILENAME\'.'), - type: ['string', 'array'], - items: { - type: 'string' - } - }, - description: { - description: nls.localize('snippetSchema.json.description', 'The snippet description.'), - type: ['string', 'array'] - } - }, + properties: snippetSchemaProperties, additionalProperties: false } }; @@ -75,24 +78,10 @@ const globalSchema: IJSONSchema = { type: 'object', required: ['prefix', 'body'], properties: { - prefix: { - description: nls.localize('snippetSchema.json.prefix', 'The prefix to used when selecting the snippet in intellisense'), - type: ['string', 'array'] - }, + ...snippetSchemaProperties, scope: { description: nls.localize('snippetSchema.json.scope', "A list of language names to which this snippet applies, e.g. 'typescript,javascript'."), type: 'string' - }, - body: { - description: nls.localize('snippetSchema.json.body', 'The snippet content. Use \'$1\', \'${1:defaultText}\' to define cursor positions, use \'$0\' for the final cursor position. Insert variable values with \'${varName}\' and \'${varName:defaultText}\', e.g. \'This is file: $TM_FILENAME\'.'), - type: ['string', 'array'], - items: { - type: 'string' - } - }, - description: { - description: nls.localize('snippetSchema.json.description', 'The snippet description.'), - type: 'string' } }, additionalProperties: false diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 21a1107e650..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'; @@ -116,7 +115,7 @@ namespace snippetExt { function watch(service: IFileService, resource: URI, callback: (type: FileChangeType, resource: URI) => any): IDisposable { return combinedDisposable( service.watch(resource), - service.onFileChanges(e => { + service.onDidFilesChange(e => { for (const change of e.changes) { if (resources.isEqualOrParent(change.resource, resource)) { callback(change.type, change.resource); @@ -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 { @@ -277,7 +277,7 @@ class SnippetsService implements ISnippetsService { this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket); } else { // watch - bucket.add(this._fileService.onFileChanges(e => { + bucket.add(this._fileService.onDidFilesChange(e => { if (e.contains(snippetFolder, FileChangeType.ADDED)) { this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket); } @@ -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/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index f37af65df09..70d7065de59 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -8,7 +8,7 @@ import { SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser import { Position } from 'vs/editor/common/core/position'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; @@ -67,7 +67,7 @@ suite('SnippetsService', function () { test('snippet completions - simple', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - const model = TextModel.createFromString('', undefined, modeService.getLanguageIdentifier('fooLang')); + const model = createTextModel('', undefined, modeService.getLanguageIdentifier('fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.incomplete, undefined); @@ -78,7 +78,7 @@ suite('SnippetsService', function () { test('snippet completions - with prefix', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - const model = TextModel.createFromString('bar', undefined, modeService.getLanguageIdentifier('fooLang')); + const model = createTextModel('bar', undefined, modeService.getLanguageIdentifier('fooLang')); return provider.provideCompletionItems(model, new Position(1, 4), context)!.then(result => { assert.equal(result.incomplete, undefined); @@ -113,7 +113,7 @@ suite('SnippetsService', function () { )]); const provider = new SnippetCompletionProvider(modeService, snippetService); - const model = TextModel.createFromString('bar-bar', undefined, modeService.getLanguageIdentifier('fooLang')); + const model = createTextModel('bar-bar', undefined, modeService.getLanguageIdentifier('fooLang')); await provider.provideCompletionItems(model, new Position(1, 3), context)!.then(result => { assert.equal(result.incomplete, undefined); @@ -174,19 +174,19 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('\t { assert.equal(result.suggestions.length, 1); model.dispose(); - model = TextModel.createFromString('\t { assert.equal(result.suggestions.length, 1); assert.equal((result.suggestions[0].range as any).insert.startColumn, 2); model.dispose(); - model = TextModel.createFromString('a { assert.equal(result.suggestions.length, 1); @@ -209,7 +209,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('\n\t\n>/head>', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('\n\t\n>/head>', undefined, modeService.getLanguageIdentifier('fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.suggestions.length, 1); return provider.provideCompletionItems(model, new Position(2, 2), context)!; @@ -239,7 +239,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('', undefined, modeService.getLanguageIdentifier('fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.suggestions.length, 2); let [first, second] = result.suggestions; @@ -266,7 +266,7 @@ suite('SnippetsService', function () { )]); const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('p-', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('p-', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 1); @@ -291,7 +291,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; assert.equal(result.suggestions.length, 1); @@ -310,7 +310,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString(':', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel(':', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 0); @@ -329,7 +329,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('template', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('template', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; assert.equal(result.suggestions.length, 1); @@ -352,7 +352,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; assert.equal(result.suggestions.length, 1); @@ -374,7 +374,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('.🐷-a-b', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('.🐷-a-b', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 8), context)!; assert.equal(result.suggestions.length, 1); @@ -395,7 +395,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('a ', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('a ', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); @@ -422,14 +422,14 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString(' <', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel(' <', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); let [first] = result.suggestions; assert.equal((first.range as any).insert.startColumn, 2); - model = TextModel.createFromString('1', undefined, modeService.getLanguageIdentifier('fooLang')); + model = createTextModel('1', undefined, modeService.getLanguageIdentifier('fooLang')); result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 1); @@ -450,7 +450,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('not wordFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('not wordFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); @@ -458,7 +458,7 @@ suite('SnippetsService', function () { assert.equal((first.range as any).insert.endColumn, 3); assert.equal((first.range as any).replace.endColumn, 9); - model = TextModel.createFromString('not woFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); + model = createTextModel('not woFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); @@ -466,7 +466,7 @@ suite('SnippetsService', function () { assert.equal((first.range as any).insert.endColumn, 3); assert.equal((first.range as any).replace.endColumn, 3); - model = TextModel.createFromString('not word', undefined, modeService.getLanguageIdentifier('fooLang')); + model = createTextModel('not word', undefined, modeService.getLanguageIdentifier('fooLang')); result = await provider.provideCompletionItems(model, new Position(1, 1), context)!; assert.equal(result.suggestions.length, 1); 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/surveys/browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index 08f28124a04..aec8abafbca 100644 --- a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -53,8 +53,7 @@ class LanguageSurvey extends Disposable { // Process model-save event every 250ms to reduce load const onModelsSavedWorker = this._register(new RunOnceWorker(models => { models.forEach(m => { - const model = modelService.getModel(m.resource); - if (model && model.getModeId() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) { + if (m.getMode() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) { const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL); storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL); diff --git a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts index fd820d40690..f0a1de61d5c 100644 --- a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts @@ -6,8 +6,7 @@ import * as crypto from 'crypto'; import { IFileService, IResolveFileResult, IFileStat } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IWorkbenchEnvironmentService, IEnvironmentConfiguration } from 'vs/workbench/services/environment/common/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { INotificationService, NeverShowAgainScope, INeverShowAgainOptions } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -39,6 +38,7 @@ const ModulesToLookFor = [ '@ionic', 'vue', 'tns-core-modules', + 'electron', // Other interesting packages 'aws-sdk', 'aws-amplify', @@ -199,6 +199,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.lerna" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.just-task" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.beachball" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.electron" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.bower" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.yeoman.code.ext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.cordova.high" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -246,7 +247,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.botframework-connector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ - private resolveWorkspaceTags(configuration: IWindowConfiguration, participant?: (rootFiles: string[]) => void): Promise { + private resolveWorkspaceTags(configuration: IEnvironmentConfiguration, participant?: (rootFiles: string[]) => void): Promise { const tags: Tags = Object.create(null); const state = this.contextService.getWorkbenchState(); @@ -477,12 +478,12 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { } } - private findFolders(configuration: IWindowConfiguration): URI[] | undefined { + private findFolders(configuration: IEnvironmentConfiguration): URI[] | undefined { const folder = this.findFolder(configuration); return folder && [folder]; } - private findFolder({ filesToOpenOrCreate, filesToDiff }: IWindowConfiguration): URI | undefined { + private findFolder({ filesToOpenOrCreate, filesToDiff }: IEnvironmentConfiguration): URI | undefined { if (filesToOpenOrCreate && filesToOpenOrCreate.length) { return this.parentURI(filesToOpenOrCreate[0].fileUri); } else if (filesToDiff && filesToDiff.length) { diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 9fef6bbe46e..7fcc3cb309f 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -57,7 +57,7 @@ import { TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE, TaskRunSource, KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/contrib/tasks/common/tasks'; -import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult, USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates'; import * as TaskConfig from '../common/taskConfiguration'; @@ -74,20 +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'; - -const SETTINGS_GROUP_KEY = 'settings'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; @@ -139,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(); @@ -188,10 +180,6 @@ class TaskMap { } } -interface TaskQuickPickEntry extends IQuickPickItem { - task: Task | undefined | null; -} - interface ProblemMatcherDisableMetrics { type: string; } @@ -203,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'; @@ -221,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; @@ -264,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(); @@ -553,7 +543,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } return this.getGroupedTasks().then((map) => { let values = map.get(folder); - values = values.concat(map.get(SETTINGS_GROUP_KEY)); + values = values.concat(map.get(USER_TASKS_GROUP_KEY)); if (!values) { return undefined; @@ -562,6 +552,40 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + 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; + } + } + + 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 { @@ -593,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 : []); } @@ -618,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) { @@ -631,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) { @@ -641,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) { @@ -648,13 +761,28 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private setRecentlyUsedTask(key: string): void { - this.getRecentlyUsedTasks().set(key, key); - this.saveRecentlyUsedTasks(); + private async setRecentlyUsedTask(task: Task): Promise { + let key = task.getRecentlyUsedKey(); + if (!InMemoryTask.is(task) && key) { + const customizations = this.createCustomizableTask(task); + if (ContributedTask.is(task) && customizations) { + let custom: CustomTask[] = []; + let customized: IStringDictionary = Object.create(null); + await this.computeTasksForSingleConfig(task._source.workspaceFolder ?? this.workspaceFolders[0], { + version: '2.0.0', + tasks: [customizations] + }, TaskRunSource.System, custom, customized, TaskConfig.TaskConfigSource.TasksJson, true); + for (const configuration in customized) { + key = customized[configuration].getRecentlyUsedKey()!; + } + } + this.getRecentlyUsedTasks().set(key, JSON.stringify(customizations)); + this.saveRecentlyUsedTasks(); + } } private saveRecentlyUsedTasks(): void { - if (!this._taskSystem || !this._recentlyUsedTasks) { + if (!this._recentlyUsedTasks) { return; } const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); @@ -662,11 +790,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 { @@ -711,18 +843,19 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!task) { throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Task to execute is undefined'), TaskErrors.TaskNotFound); } - return 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); + } + } else { + resolve(this.executeTask(task, resolver)); } - return this.executeTask(task, resolver); }).then((value) => { if (runSource === TaskRunSource.User) { this.getWorkspaceTasks().then(workspaceTasks => { @@ -916,7 +1049,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); } @@ -970,23 +1103,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 = { @@ -1002,8 +1122,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]; @@ -1011,10 +1160,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; @@ -1108,7 +1253,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) { @@ -1125,7 +1270,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); @@ -1171,8 +1316,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); let resolver: ITaskResolver = { - resolve: (uri: URI, alias: string) => { - let data = resolverData.get(uri.toString()); + resolve: async (uri: URI | string, alias: string) => { + let data = resolverData.get(typeof uri === 'string' ? uri : uri.toString()); if (!data) { return undefined; } @@ -1211,35 +1356,38 @@ 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, identifier: string | TaskIdentifier | undefined) => { - let data = uri ? resolverData.get(uri.toString()) : 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; } @@ -1255,14 +1403,14 @@ 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); }); }); } - private handleExecuteResult(executeResult: ITaskExecuteResult): Promise { + private async handleExecuteResult(executeResult: ITaskExecuteResult): Promise { if (executeResult.task.taskLoadMessages && executeResult.task.taskLoadMessages.length > 0) { executeResult.task.taskLoadMessages.forEach(loadMessage => { this._outputChannel.append(loadMessage + '\n'); @@ -1270,10 +1418,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.showOutput(); } - let key = executeResult.task.getRecentlyUsedKey(); - if (key) { - this.setRecentlyUsedTask(key); - } + await this.setRecentlyUsedTask(executeResult.task); if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; if (active && active.same) { @@ -1337,7 +1482,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; @@ -1349,36 +1494,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; @@ -1411,7 +1533,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 { @@ -1496,6 +1618,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)) { @@ -1529,7 +1654,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. + await this.migrateRecentTasks(result.all()); + } return result; }, () => { // If we can't read the tasks.json file provide at least the contributed tasks @@ -1600,7 +1728,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } const userTasks = await this.computeUserTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); if (userTasks) { - result.set(SETTINGS_GROUP_KEY, userTasks); + result.set(USER_TASKS_GROUP_KEY, userTasks); } const workspaceFileTasks = await this.computeWorkspaceFileTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); if (workspaceFileTasks && this._workspace && this._workspace.configuration) { @@ -1717,13 +1845,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); @@ -1960,19 +2088,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 }; @@ -2025,7 +2152,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)); @@ -2042,6 +2171,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; @@ -2094,32 +2227,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; }); @@ -2144,6 +2252,32 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + private needsRecentTasksMigration(): boolean { + return (this.getRecentlyUsedTasksV1().size > 0) && (this.getRecentlyUsedTasks().size === 0); + } + + public async 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; + } + }); + const reversed = recentlyUsedTasks.keys().reverse(); + for (const key in reversed) { + let task = taskMap[key]; + if (task) { + await 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); @@ -2171,11 +2305,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 = this.contextService.getWorkspace().folders; + let folders: (IWorkspaceFolder | string)[] = this.contextService.getWorkspace().folders; + folders = folders.concat([USER_TASKS_GROUP_KEY]); for (let folder of folders) { - let task = resolver.resolve(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 @@ -2194,15 +2329,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private doRunTaskCommand(tasks?: Task[]): void { this.showIgnoredFoldersMessage().then(() => { - this.showQuickPick(tasks ? tasks : this.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; } @@ -2223,7 +2356,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); @@ -2477,6 +2610,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) => { @@ -2485,9 +2622,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) { @@ -2554,7 +2691,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private handleSelection(selection: TaskQuickPickEntryType) { + private handleSelection(selection: TaskQuickPickEntryType | undefined) { if (!selection) { return; } @@ -2565,7 +2702,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 ae20fb999fe..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; @@ -438,6 +445,7 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); promise = this.executeTask(dependencyTask, resolver, trigger, alreadyResolved); } + promises.push(promise); if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { const promiseResult = await promise; if (promiseResult.exitCode === 0) { @@ -463,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 }; } } @@ -544,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) { @@ -562,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); @@ -605,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 { @@ -619,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 5e6b05391ab..eb5b719dfeb 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -22,6 +22,8 @@ import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/works import * as Tasks from './tasks'; import { TaskDefinitionRegistry } from './taskDefinitionRegistry'; import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { URI } from 'vs/base/common/uri'; +import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; export const enum ShellQuoting { @@ -679,6 +681,7 @@ export namespace RunOnOptions { } export namespace RunOptions { + const properties: MetaData[] = [{ property: 'reevaluateOnRerun' }, { property: 'runOn' }, { property: 'instanceLimit' }]; export function fromConfiguration(value: RunOptionsConfig | undefined): Tasks.RunOptions { return { reevaluateOnRerun: value ? value.reevaluateOnRerun : true, @@ -686,6 +689,14 @@ export namespace RunOptions { instanceLimit: value ? value.instanceLimit : 1 }; } + + export function assignProperties(target: Tasks.RunOptions, source: Tasks.RunOptions | undefined): Tasks.RunOptions { + return _assignProperties(target, source, properties)!; + } + + export function fillProperties(target: Tasks.RunOptions, source: Tasks.RunOptions | undefined): Tasks.RunOptions { + return _fillProperties(target, source, properties)!; + } } interface ParseContext { @@ -1231,12 +1242,20 @@ namespace GroupKind { } namespace TaskDependency { - export function from(this: void, external: string | TaskIdentifier, context: ParseContext): Tasks.TaskDependency | undefined { + 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)) { - return { uri: context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.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 { @@ -1267,7 +1286,7 @@ namespace ConfigurationProperties { { property: 'options' } ]; - export function from(this: void, external: ConfigurationProperties & { [key: string]: any; }, context: ParseContext, includeCommandOptions: boolean, properties?: IJSONSchemaMap): Tasks.ConfigurationProperties | undefined { + export function from(this: void, external: ConfigurationProperties & { [key: string]: any; }, context: ParseContext, includeCommandOptions: boolean, source: TaskConfigSource, properties?: IJSONSchemaMap): Tasks.ConfigurationProperties | undefined { if (!external) { return undefined; } @@ -1311,14 +1330,14 @@ namespace ConfigurationProperties { if (external.dependsOn !== undefined) { if (Types.isArray(external.dependsOn)) { result.dependsOn = external.dependsOn.reduce((dependencies: Tasks.TaskDependency[], item): Tasks.TaskDependency[] => { - const dependency = TaskDependency.from(item, context); + const dependency = TaskDependency.from(item, context, source); if (dependency) { dependencies.push(dependency); } return dependencies; }, []); } else { - const dependsOnValue = TaskDependency.from(external.dependsOn, context); + const dependsOnValue = TaskDependency.from(external.dependsOn, context, source); result.dependsOn = dependsOnValue ? [dependsOnValue] : undefined; } } @@ -1435,7 +1454,7 @@ namespace ConfiguringTask { RunOptions.fromConfiguration(external.runOptions), {} ); - let configuration = ConfigurationProperties.from(external, context, true, typeDeclaration.properties); + let configuration = ConfigurationProperties.from(external, context, true, source, typeDeclaration.properties); if (configuration) { result.configurationProperties = Objects.assign(result.configurationProperties, configuration); if (result.configurationProperties.name) { @@ -1512,7 +1531,7 @@ namespace CustomTask { identifier: taskName, } ); - let configuration = ConfigurationProperties.from(external, context, false); + let configuration = ConfigurationProperties.from(external, context, false, source); if (configuration) { result.configurationProperties = Objects.assign(result.configurationProperties, configuration); } @@ -1601,6 +1620,7 @@ namespace CustomTask { result.command.presentation = CommandConfiguration.PresentationOptions.assignProperties( result.command.presentation!, configuredProps.configurationProperties.presentation)!; result.command.options = CommandOptions.assignProperties(result.command.options, configuredProps.configurationProperties.options); + result.runOptions = RunOptions.assignProperties(result.runOptions, configuredProps.runOptions); let contributedConfigProps: Tasks.ConfigurationProperties = contributedTask.configurationProperties; fillProperty(resultConfigProps, contributedConfigProps, 'group'); @@ -1613,6 +1633,7 @@ namespace CustomTask { result.command.presentation = CommandConfiguration.PresentationOptions.fillProperties( result.command.presentation!, contributedConfigProps.presentation)!; result.command.options = CommandOptions.fillProperties(result.command.options, contributedConfigProps.options); + result.runOptions = RunOptions.fillProperties(result.runOptions, contributedTask.runOptions); if (contributedTask.hasDefinedMatchers === true) { result.hasDefinedMatchers = true; @@ -2058,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(); @@ -2073,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 fb2167017af..38fd29f0995 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -50,6 +50,8 @@ export interface WorkspaceFolderTaskResult extends WorkspaceTaskResult { workspaceFolder: IWorkspaceFolder; } +export const USER_TASKS_GROUP_KEY = 'settings'; + export interface ITaskService { _serviceBrand: undefined; onDidStateChange: Event; @@ -67,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[]): Promise; 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 888d79b912c..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, 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 8130ab56092..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 { @@ -438,7 +448,7 @@ export interface KeyedTaskIdentifier extends TaskIdentifier { } export interface TaskDependency { - uri: URI; + uri: URI | string; task: string | KeyedTaskIdentifier | undefined; } @@ -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 6f94e27d1a6..bd8c574e3d5 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -19,8 +19,8 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { ITextFileService, ITextFileModelSaveEvent, ITextFileModelLoadEvent } from 'vs/workbench/services/textfile/common/textfiles'; -import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { ITextFileService, ITextFileSaveEvent, ITextFileLoadEvent } from 'vs/workbench/services/textfile/common/textfiles'; +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'; @@ -58,7 +58,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IConfigurationService configurationService: IConfigurationService, @IViewletService viewletService: IViewletService, - @ITextFileService textFileService: ITextFileService, + @ITextFileService textFileService: ITextFileService ) { super(); @@ -131,7 +131,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr this._register(lifecycleService.onShutdown(() => this.dispose())); } - private onTextFileModelLoaded(e: ITextFileModelLoadEvent): void { + private onTextFileModelLoaded(e: ITextFileLoadEvent): void { const settingsType = this.getTypeIfSettings(e.model.resource); if (settingsType) { type SettingsReadClassification = { @@ -146,7 +146,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr } } - private onTextFileModelSaved(e: ITextFileModelSaveEvent): void { + private onTextFileModelSaved(e: ITextFileSaveEvent): void { const settingsType = this.getTypeIfSettings(e.model.resource); if (settingsType) { type SettingsWrittenClassification = { @@ -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..9be3f6db4ea 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -155,3 +155,43 @@ .xterm.xterm-cursor-pointer { cursor: pointer!important; } + +.monaco-workbench .part.sidebar > .title > .title-actions .switch-terminal, +.monaco-pane-view .pane > .pane-header .monaco-action-bar .switch-terminal { + border-width: 1px; + border-style: solid; +} + +.part.panel > .title > .title-actions .switch-terminal > .monaco-select-box { + border-width: 1px; + border-style: solid; +} + +.monaco-workbench .part.sidebar > .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.sidebar > .title > .title-actions .switch-terminal { + border-radius: 4px; +} + +.monaco-workbench .part.sidebar > .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.sidebar > .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..25a2010e1c9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -10,387 +10,87 @@ import 'vs/css!./media/terminal'; import 'vs/css!./media/widgets'; import 'vs/css!./media/xterm'; import * as nls from 'vs/nls'; -import { SyncActionDescriptor, registerAction2 } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; 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 { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight, KeybindingsRegistry, IKeybindings } 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 { registerTerminalActions, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, KillTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, ToggleTerminalAction, terminalSendSequenceCommand } 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 { KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_VIEW_ID, TERMINAL_ACTION_CATEGORY, 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'; import { setupTerminalMenu } from 'vs/workbench/contrib/terminal/common/terminalMenu'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { DEFAULT_COMMANDS_TO_SKIP_SHELL } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; import { TerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig'; -import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; -import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, WindowsShellType } 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'; +import { terminalConfiguration, getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration'; +// Register services registerSingleton(ITerminalService, TerminalService, true); +// Register quick accesses +const quickAccessRegistry = (Registry.as(QuickAccessExtensions.Quickaccess)); +const inTerminalsPicker = 'inTerminalPicker'; +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 quickAccessNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker'; +CommandsRegistry.registerCommand({ id: quickAccessNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickAccessNavigateNextInTerminalPickerId, true) }); +const quickAccessNavigatePreviousInTerminalPickerId = 'workbench.action.quickOpenNavigatePreviousInTerminalPicker'; +CommandsRegistry.registerCommand({ id: quickAccessNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickAccessNavigatePreviousInTerminalPickerId, false) }); + +// Register configurations +const configurationRegistry = Registry.as(Extensions.Configuration); +configurationRegistry.registerConfiguration(terminalConfiguration); if (platform.isWeb) { - registerShellConfiguration(); + // Desktop shell configuration are registered in electron-browser as their default values rely + // on process.env + configurationRegistry.registerConfiguration(getTerminalShellConfiguration()); } -const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Quickopen)); - -const inTerminalsPicker = 'inTerminalPicker'; - -quickOpenRegistry.registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - TerminalPickerHandler, - TerminalPickerHandler.ID, - TERMINAL_PICKER_PREFIX, - inTerminalsPicker, - nls.localize('quickOpen.terminal', "Show All Opened Terminals") - ) -); - -const quickOpenNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker'; -CommandsRegistry.registerCommand( - { id: quickOpenNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigateNextInTerminalPickerId, true) }); - -const quickOpenNavigatePreviousInTerminalPickerId = 'workbench.action.quickOpenNavigatePreviousInTerminalPicker'; -CommandsRegistry.registerCommand( - { id: quickOpenNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigatePreviousInTerminalPickerId, false) }); - - -const configurationRegistry = Registry.as(Extensions.Configuration); -configurationRegistry.registerConfiguration({ - id: 'terminal', - order: 100, - title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), - type: 'object', - properties: { - 'terminal.integrated.automationShell.linux': { - markdownDescription: nls.localize('terminal.integrated.automationShell.linux', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`'), - type: ['string', 'null'], - default: null - }, - 'terminal.integrated.automationShell.osx': { - markdownDescription: nls.localize('terminal.integrated.automationShell.osx', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`'), - type: ['string', 'null'], - default: null - }, - 'terminal.integrated.automationShell.windows': { - markdownDescription: nls.localize('terminal.integrated.automationShell.windows', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`'), - type: ['string', 'null'], - default: null - }, - 'terminal.integrated.shellArgs.linux': { - markdownDescription: nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: 'array', - items: { - type: 'string' - }, - default: [] - }, - 'terminal.integrated.shellArgs.osx': { - markdownDescription: nls.localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: 'array', - items: { - type: 'string' - }, - // Unlike on Linux, ~/.profile is not sourced when logging into a macOS session. This - // is the reason terminals on macOS typically run login shells by default which set up - // the environment. See http://unix.stackexchange.com/a/119675/115410 - default: ['-l'] - }, - 'terminal.integrated.shellArgs.windows': { - markdownDescription: nls.localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - 'anyOf': [ - { - type: 'array', - items: { - type: 'string', - markdownDescription: nls.localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).") - }, - }, - { - type: 'string', - markdownDescription: nls.localize('terminal.integrated.shellArgs.windows.string', "The command line arguments in [command-line format](https://msdn.microsoft.com/en-au/08dfcab2-eb6e-49a4-80eb-87d4076c98c6) to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).") - } - ], - default: [] - }, - 'terminal.integrated.macOptionIsMeta': { - description: nls.localize('terminal.integrated.macOptionIsMeta', "Controls whether to treat the option key as the meta key in the terminal on macOS."), - type: 'boolean', - default: false - }, - 'terminal.integrated.macOptionClickForcesSelection': { - description: nls.localize('terminal.integrated.macOptionClickForcesSelection', "Controls whether to force selection when using Option+click on macOS. This will force a regular (line) selection and disallow the use of column selection mode. This enables copying and pasting using the regular terminal selection, for example, when mouse mode is enabled in tmux."), - type: 'boolean', - default: false - }, - 'terminal.integrated.copyOnSelection': { - description: nls.localize('terminal.integrated.copyOnSelection', "Controls whether text selected in the terminal will be copied to the clipboard."), - type: 'boolean', - default: false - }, - 'terminal.integrated.drawBoldTextInBrightColors': { - description: nls.localize('terminal.integrated.drawBoldTextInBrightColors', "Controls whether bold text in the terminal will always use the \"bright\" ANSI color variant."), - type: 'boolean', - default: true - }, - 'terminal.integrated.fontFamily': { - markdownDescription: nls.localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."), - type: 'string' - }, - // TODO: Support font ligatures - // 'terminal.integrated.fontLigatures': { - // 'description': nls.localize('terminal.integrated.fontLigatures', "Controls whether font ligatures are enabled in the terminal."), - // 'type': 'boolean', - // 'default': false - // }, - 'terminal.integrated.fontSize': { - description: nls.localize('terminal.integrated.fontSize', "Controls the font size in pixels of the terminal."), - type: 'number', - default: EDITOR_FONT_DEFAULTS.fontSize - }, - 'terminal.integrated.letterSpacing': { - description: nls.localize('terminal.integrated.letterSpacing', "Controls the letter spacing of the terminal, this is an integer value which represents the amount of additional pixels to add between characters."), - type: 'number', - default: DEFAULT_LETTER_SPACING - }, - 'terminal.integrated.lineHeight': { - description: nls.localize('terminal.integrated.lineHeight', "Controls the line height of the terminal, this number is multiplied by the terminal font size to get the actual line-height in pixels."), - type: 'number', - default: DEFAULT_LINE_HEIGHT - }, - 'terminal.integrated.minimumContrastRatio': { - markdownDescription: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: [WCAG AA compliance (minimum)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html).\n- 7: [WCAG AAA compliance (enhanced)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast7.html).\n- 21: White on black or black on white."), - type: 'number', - default: 1 - }, - 'terminal.integrated.fastScrollSensitivity': { - markdownDescription: nls.localize('terminal.integrated.fastScrollSensitivity', "Scrolling speed multiplier when pressing `Alt`."), - type: 'number', - default: 5 - }, - 'terminal.integrated.mouseWheelScrollSensitivity': { - markdownDescription: nls.localize('terminal.integrated.mouseWheelScrollSensitivity', "A multiplier to be used on the `deltaY` of mouse wheel scroll events."), - type: 'number', - default: 1 - }, - 'terminal.integrated.fontWeight': { - type: 'string', - enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - description: nls.localize('terminal.integrated.fontWeight', "The font weight to use within the terminal for non-bold text."), - default: 'normal' - }, - 'terminal.integrated.fontWeightBold': { - type: 'string', - enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - description: nls.localize('terminal.integrated.fontWeightBold', "The font weight to use within the terminal for bold text."), - default: 'bold' - }, - 'terminal.integrated.cursorBlinking': { - description: nls.localize('terminal.integrated.cursorBlinking', "Controls whether the terminal cursor blinks."), - type: 'boolean', - default: false - }, - 'terminal.integrated.cursorStyle': { - description: nls.localize('terminal.integrated.cursorStyle', "Controls the style of terminal cursor."), - enum: [TerminalCursorStyle.BLOCK, TerminalCursorStyle.LINE, TerminalCursorStyle.UNDERLINE], - default: TerminalCursorStyle.BLOCK - }, - 'terminal.integrated.cursorWidth': { - markdownDescription: nls.localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when `#terminal.integrated.cursorStyle#` is set to `line`."), - type: 'number', - default: 1 - }, - 'terminal.integrated.scrollback': { - description: nls.localize('terminal.integrated.scrollback', "Controls the maximum amount of lines the terminal keeps in its buffer."), - type: 'number', - default: 1000 - }, - 'terminal.integrated.detectLocale': { - markdownDescription: nls.localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since VS Code's terminal only supports UTF-8 encoded data coming from the shell."), - type: 'string', - enum: ['auto', 'off', 'on'], - markdownEnumDescriptions: [ - nls.localize('terminal.integrated.detectLocale.auto', "Set the `$LANG` environment variable if the existing variable does not exist or it does not end in `'.UTF-8'`."), - nls.localize('terminal.integrated.detectLocale.off', "Do not set the `$LANG` environment variable."), - nls.localize('terminal.integrated.detectLocale.on', "Always set the `$LANG` environment variable.") - ], - default: 'auto' - }, - 'terminal.integrated.rendererType': { - type: 'string', - enum: ['auto', 'canvas', 'dom', 'experimentalWebgl'], - markdownEnumDescriptions: [ - nls.localize('terminal.integrated.rendererType.auto', "Let VS Code guess which renderer to use."), - nls.localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer."), - nls.localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer."), - nls.localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) and this will only be enabled for new terminals (not hot swappable like the other renderers).") - ], - default: 'auto', - description: nls.localize('terminal.integrated.rendererType', "Controls how the terminal is rendered.") - }, - 'terminal.integrated.rightClickBehavior': { - type: 'string', - enum: ['default', 'copyPaste', 'paste', 'selectWord'], - enumDescriptions: [ - nls.localize('terminal.integrated.rightClickBehavior.default', "Show the context menu."), - nls.localize('terminal.integrated.rightClickBehavior.copyPaste', "Copy when there is a selection, otherwise paste."), - nls.localize('terminal.integrated.rightClickBehavior.paste', "Paste on right click."), - nls.localize('terminal.integrated.rightClickBehavior.selectWord', "Select the word under the cursor and show the context menu.") - ], - default: platform.isMacintosh ? 'selectWord' : platform.isWindows ? 'copyPaste' : 'default', - description: nls.localize('terminal.integrated.rightClickBehavior', "Controls how terminal reacts to right click.") - }, - 'terminal.integrated.cwd': { - description: nls.localize('terminal.integrated.cwd', "An explicit start path where the terminal will be launched, this is used as the current working directory (cwd) for the shell process. This may be particularly useful in workspace settings if the root directory is not a convenient cwd."), - type: 'string', - default: undefined - }, - 'terminal.integrated.confirmOnExit': { - description: nls.localize('terminal.integrated.confirmOnExit', "Controls whether to confirm on exit if there are active terminal sessions."), - type: 'boolean', - default: false - }, - 'terminal.integrated.enableBell': { - description: nls.localize('terminal.integrated.enableBell', "Controls whether the terminal bell is enabled."), - type: 'boolean', - default: false - }, - 'terminal.integrated.commandsToSkipShell': { - markdownDescription: nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')), - type: 'array', - items: { - type: 'string' - }, - default: [] - }, - 'terminal.integrated.allowChords': { - markdownDescription: nls.localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `#terminal.integrated.commandsToSkipShell#`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), - type: 'boolean', - default: true - }, - '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', - default: true - }, - 'terminal.integrated.env.osx': { - markdownDescription: nls.localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the VS Code process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), - type: 'object', - additionalProperties: { - type: ['string', 'null'] - }, - default: {} - }, - 'terminal.integrated.env.linux': { - markdownDescription: nls.localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), - type: 'object', - additionalProperties: { - type: ['string', 'null'] - }, - default: {} - }, - 'terminal.integrated.env.windows': { - markdownDescription: nls.localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), - type: 'object', - additionalProperties: { - type: ['string', 'null'] - }, - default: {} - }, - 'terminal.integrated.showExitAlert': { - description: nls.localize('terminal.integrated.showExitAlert', "Controls whether to show the alert \"The terminal process terminated with exit code\" when exit code is non-zero."), - type: 'boolean', - default: true - }, - 'terminal.integrated.splitCwd': { - description: nls.localize('terminal.integrated.splitCwd', "Controls the working directory a split terminal starts with."), - type: 'string', - enum: ['workspaceRoot', 'initial', 'inherited'], - enumDescriptions: [ - nls.localize('terminal.integrated.splitCwd.workspaceRoot', "A new split terminal will use the workspace root as the working directory. In a multi-root workspace a choice for which root folder to use is offered."), - nls.localize('terminal.integrated.splitCwd.initial', "A new split terminal will use the working directory that the parent terminal started with."), - nls.localize('terminal.integrated.splitCwd.inherited', "On macOS and Linux, a new split terminal will use the working directory of the parent terminal. On Windows, this behaves the same as initial."), - ], - default: 'inherited' - }, - 'terminal.integrated.windowsEnableConpty': { - description: nls.localize('terminal.integrated.windowsEnableConpty', "Whether to use ConPTY for Windows terminal process communication (requires Windows 10 build number 18309+). Winpty will be used if this is false."), - type: 'boolean', - default: true - }, - 'terminal.integrated.experimentalUseTitleEvent': { - description: nls.localize('terminal.integrated.experimentalUseTitleEvent', "An experimental setting that will use the terminal title event for the dropdown title. This setting will only apply to new terminals."), - type: 'boolean', - default: false - }, - 'terminal.integrated.enableFileLinks': { - description: nls.localize('terminal.integrated.enableFileLinks', "Whether to enable file links in the terminal. Links can be slow when working on a network drive in particular because each file link is verified against the file system."), - type: 'boolean', - default: true - }, - 'terminal.integrated.unicodeVersion': { - type: 'string', - enum: ['6', '11'], - enumDescriptions: [ - nls.localize('terminal.integrated.unicodeVersion.six', "Version 6 of unicode, this is an older version which should work better on older systems."), - nls.localize('terminal.integrated.unicodeVersion.eleven', "Version 11 of unicode, this version provides better support on modern systems that use modern versions of unicode.") - ], - default: '11', - description: nls.localize('terminal.integrated.unicodeVersion', "Controls what version of unicode to use when evaluating the width of characters in the terminal. If you experience emoji or other wide characters not taking up the right amount of space or backspace either deleting too much or too little then you may want to try tweaking this setting.") - } - } -}); - -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); - +// Register views 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); -// On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl -const category = TERMINAL_ACTION_CATEGORY; +// Register actions const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); +registerTerminalActions(); +const category = TERMINAL_ACTION_CATEGORY; actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK } }), 'Terminal: Create New Integrated Terminal', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSelectionTerminalAction, ClearSelectionTerminalAction.ID, ClearSelectionTerminalAction.LABEL, { - primary: KeyCode.Escape, - linux: { primary: KeyCode.Escape } -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE)), 'Terminal: Clear Selection', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(CreateNewInActiveWorkspaceTerminalAction, CreateNewInActiveWorkspaceTerminalAction.ID, CreateNewInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (In Active Workspace)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusActiveTerminalAction, FocusActiveTerminalAction.ID, FocusActiveTerminalAction.LABEL), 'Terminal: Focus Terminal', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectAllTerminalAction, SelectAllTerminalAction.ID, SelectAllTerminalAction.LABEL, { // Don't use ctrl+a by default as that would override the common go to start // of prompt shell binding @@ -400,73 +100,17 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectAllTerm // makes it easier for users to see how it works though. mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_A } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select All', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RunSelectedTextInTerminalAction, RunSelectedTextInTerminalAction.ID, RunSelectedTextInTerminalAction.LABEL), 'Terminal: Run Selected Text In Active Terminal', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RunActiveFileInTerminalAction, RunActiveFileInTerminalAction.ID, RunActiveFileInTerminalAction.LABEL), 'Terminal: Run Active File In Active Terminal', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyCode.US_BACKTICK } }), 'View: Toggle Integrated Terminal', nls.localize('viewCategory', "View")); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollDownTerminalAction, ScrollDownTerminalAction.ID, ScrollDownTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down (Line)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollDownPageTerminalAction, ScrollDownPageTerminalAction.ID, ScrollDownPageTerminalAction.LABEL, { - primary: KeyMod.Shift | KeyCode.PageDown, - mac: { primary: KeyCode.PageDown } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down (Page)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToBottomTerminalAction, ScrollToBottomTerminalAction.ID, ScrollToBottomTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.End, - linux: { primary: KeyMod.Shift | KeyCode.End } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll to Bottom', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollUpTerminalAction, ScrollUpTerminalAction.ID, ScrollUpTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up (Line)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollUpPageTerminalAction, ScrollUpPageTerminalAction.ID, ScrollUpPageTerminalAction.LABEL, { - primary: KeyMod.Shift | KeyCode.PageUp, - mac: { primary: KeyCode.PageUp } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up (Page)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToTopTerminalAction, ScrollToTopTerminalAction.ID, ScrollToTopTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.Home, - linux: { primary: KeyMod.Shift | KeyCode.Home } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll to Top', category); +// Weight is higher than work workbench contributions so the keybinding remains +// highest priority when chords are registered afterwards actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearTerminalAction, ClearTerminalAction.ID, ClearTerminalAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageWorkspaceShellPermissionsTerminalCommand, ManageWorkspaceShellPermissionsTerminalCommand.ID, ManageWorkspaceShellPermissionsTerminalCommand.LABEL), 'Terminal: Manage Workspace Shell Permissions', category); -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); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, { - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape] -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE)), 'Terminal: Hide Find Widget', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteWordLeftTerminalAction, DeleteWordLeftTerminalAction.ID, DeleteWordLeftTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.Backspace, - mac: { primary: KeyMod.Alt | KeyCode.Backspace } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete Word Left', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteWordRightTerminalAction, DeleteWordRightTerminalAction.ID, DeleteWordRightTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.Delete, - mac: { primary: KeyMod.Alt | KeyCode.Delete } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete Word Right', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(DeleteToLineStartTerminalAction, DeleteToLineStartTerminalAction.ID, DeleteToLineStartTerminalAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Delete To Line Start', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(MoveToLineStartTerminalAction, MoveToLineStartTerminalAction.ID, MoveToLineStartTerminalAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line Start', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(MoveToLineEndTerminalAction, MoveToLineEndTerminalAction.ID, MoveToLineEndTerminalAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line End', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_5, mac: { @@ -475,116 +119,6 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SplitTerminal } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Terminal', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SplitInActiveWorkspaceTerminalAction, SplitInActiveWorkspaceTerminalAction.ID, SplitInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Split Terminal (In Active Workspace)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPreviousPaneTerminalAction, FocusPreviousPaneTerminalAction.ID, FocusPreviousPaneTerminalAction.LABEL, { - primary: KeyMod.Alt | KeyCode.LeftArrow, - secondary: [KeyMod.Alt | KeyCode.UpArrow], - mac: { - primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.LeftArrow, - secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.UpArrow] - } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Previous Pane', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusNextPaneTerminalAction, FocusNextPaneTerminalAction.ID, FocusNextPaneTerminalAction.LABEL, { - primary: KeyMod.Alt | KeyCode.RightArrow, - secondary: [KeyMod.Alt | KeyCode.DownArrow], - mac: { - primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.RightArrow, - secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.DownArrow] - } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Next Pane', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneLeftTerminalAction, ResizePaneLeftTerminalAction.ID, ResizePaneLeftTerminalAction.LABEL, { - primary: 0, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow }, - mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Left', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneRightTerminalAction, ResizePaneRightTerminalAction.ID, ResizePaneRightTerminalAction.LABEL, { - primary: 0, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow }, - mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Right', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneUpTerminalAction, ResizePaneUpTerminalAction.ID, ResizePaneUpTerminalAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Up', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ResizePaneDownTerminalAction, ResizePaneDownTerminalAction.ID, ResizePaneDownTerminalAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Resize Pane Down', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow } -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Previous Command', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow } -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Next Command', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Previous Command', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToNextCommandAction, SelectToNextCommandAction.ID, SelectToNextCommandAction.LABEL, { - primary: 0, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select To Next Command', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeExitTerminalAction, NavigationModeExitTerminalAction.ID, NavigationModeExitTerminalAction.LABEL, { - primary: KeyCode.Escape -}, 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); -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); -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); -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); -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); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindNext, FindNext.ID, FindNext.LABEL, { - primary: KeyCode.F3, - mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find next', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindNext, FindNext.ID, FindNext.LABEL, { - primary: KeyCode.F3, - secondary: [KeyMod.Shift | KeyCode.Enter], - mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3, KeyMod.Shift | KeyCode.Enter] } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find next', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { - primary: KeyMod.Shift | KeyCode.F3, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] }, -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Find previous', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindPrevious, FindPrevious.ID, FindPrevious.LABEL, { - primary: KeyMod.Shift | KeyCode.F3, - secondary: [KeyCode.Enter], - mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3, KeyCode.Enter] }, -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous', category); // Commands might be affected by Web restrictons if (BrowserFeatures.clipboard.writeText) { @@ -594,83 +128,58 @@ if (BrowserFeatures.clipboard.writeText) { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C } }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Copy Selection', category); } + +function registerSendSequenceKeybinding(text: string, rule: { when?: ContextKeyExpression } & IKeybindings): void { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: TERMINAL_COMMAND_ID.SEND_SEQUENCE, + weight: KeybindingWeight.WorkbenchContrib, + when: rule.when || KEYBINDING_CONTEXT_TERMINAL_FOCUS, + primary: rule.primary, + mac: rule.mac, + linux: rule.linux, + win: rule.win, + handler: terminalSendSequenceCommand, + args: { text } + }); +} + if (BrowserFeatures.clipboard.readText) { actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V] }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); + // An extra Windows-only ctrl+v keybinding is used for pwsh that sends ctrl+v directly to the + // shell, this gets handled by PSReadLine which properly handles multi-line pastes + if (platform.isWindows) { + registerSendSequenceKeybinding(String.fromCharCode('V'.charCodeAt(0) - 64), { // ctrl+v + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, ContextKeyExpr.equals(KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, WindowsShellType.PowerShell)), + primary: KeyMod.CtrlCmd | KeyCode.KEY_V + }); + } } -registerAction2(class extends SendSequenceTerminalAction { - constructor() { - super({ - id: SendSequenceTerminalAction.ID, - title: SendSequenceTerminalAction.LABEL, - description: { - description: SendSequenceTerminalAction.LABEL, - args: [{ - name: 'args', - schema: { - type: 'object', - required: ['text'], - properties: { - text: { type: 'string' } - }, - } - }] - } - }); - } +// Delete word left: ctrl+w +registerSendSequenceKeybinding(String.fromCharCode('W'.charCodeAt(0) - 64), { + primary: KeyMod.CtrlCmd | KeyCode.Backspace, + mac: { primary: KeyMod.Alt | KeyCode.Backspace } }); -registerAction2(class extends CreateNewWithCwdTerminalAction { - constructor() { - super({ - id: CreateNewWithCwdTerminalAction.ID, - title: CreateNewWithCwdTerminalAction.LABEL, - description: { - description: CreateNewWithCwdTerminalAction.LABEL, - args: [{ - name: 'args', - schema: { - type: 'object', - required: ['cwd'], - properties: { - cwd: { - description: CreateNewWithCwdTerminalAction.CWD_ARG_LABEL, - type: 'string' - } - }, - } - }] - } - }); - } +// Delete word right: alt+d +registerSendSequenceKeybinding('\x1bd', { + primary: KeyMod.CtrlCmd | KeyCode.Delete, + mac: { primary: KeyMod.Alt | KeyCode.Delete } }); -registerAction2(class extends RenameWithArgTerminalAction { - constructor() { - super({ - id: RenameWithArgTerminalAction.ID, - title: RenameWithArgTerminalAction.LABEL, - description: { - description: RenameWithArgTerminalAction.LABEL, - args: [{ - name: 'args', - schema: { - type: 'object', - required: ['name'], - properties: { - name: { - description: RenameWithArgTerminalAction.NAME_ARG_LABEL, - type: 'string', - minLength: 1 - } - } - } - }] - } - }); - } +// Delete to line start: ctrl+u +registerSendSequenceKeybinding('\u0015', { + mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace } +}); +// Move to line start: ctrl+A +registerSendSequenceKeybinding(String.fromCharCode('A'.charCodeAt(0) - 64), { + mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow } +}); +// Move to line end: ctrl+E +registerSendSequenceKeybinding(String.fromCharCode('E'.charCodeAt(0) - 64), { + mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } }); setupTerminalCommands(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index bac468cd33a..e33616878ab 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -112,6 +112,13 @@ export interface ITerminalService { getActiveOrCreateInstance(): ITerminalInstance; splitInstance(instance: ITerminalInstance, shell?: IShellLaunchConfig): ITerminalInstance | null; + /** + * Perform an action with the active terminal instance, if the terminal does + * not exist the callback will not be called. + * @param callback The callback that fires with the active terminal + */ + doWithActiveInstance(callback: (terminal: ITerminalInstance) => T): T | void; + getActiveTab(): ITerminalTab | null; setActiveTabToNext(): void; setActiveTabToPrevious(): void; @@ -131,7 +138,15 @@ export interface ITerminalService { findNext(): void; findPrevious(): void; - selectDefaultWindowsShell(): Promise; + /** + * 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; + + selectDefaultShell(): Promise; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; manageWorkspaceShellPermissions(): void; @@ -172,13 +187,25 @@ export interface ISearchOptions { } export enum WindowsShellType { - CommandPrompt, - PowerShell, - Wsl, - GitBash + CommandPrompt = 'cmd', + PowerShell = 'pwsh', + Wsl = 'wsl', + GitBash = 'gitbash' } 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 +267,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 07f51c5d09f..56aa1d47225 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -3,29 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; 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 { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS } 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'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -34,9 +26,17 @@ import { URI } from 'vs/base/common/uri'; 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 { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +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, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { addClass } from 'vs/base/browser/dom'; +import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { localize } from 'vs/nls'; +import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise { switch (configHelper.config.splitCwd) { @@ -47,7 +47,7 @@ async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITe } else if (folders.length > 1) { // Only choose a path when there's more than 1 folder const options: IPickOptions = { - placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal") + placeHolder: localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal") }; const workspace = await commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]); if (!workspace) { @@ -65,21 +65,23 @@ 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"); + public static readonly LABEL = 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 { + async run() { if (this.terminalService.terminalInstances.length === 0) { // If there is not yet an instance attempt to create it here so that we can suggest a // new shell on Windows (and not do so when the panel is restored on reload). @@ -96,48 +98,23 @@ export class ToggleTerminalAction extends TogglePanelAction { export class KillTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.KILL; - public static readonly LABEL = nls.localize('workbench.action.terminal.kill', "Kill the Active Terminal Instance"); - public static readonly PANEL_LABEL = nls.localize('workbench.action.terminal.kill.short', "Kill Terminal"); + public static readonly LABEL = localize('workbench.action.terminal.kill', "Kill the Active Terminal Instance"); + public static readonly PANEL_LABEL = localize('workbench.action.terminal.kill.short', "Kill Terminal"); constructor( id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label, 'terminal-action codicon-trash'); } - public run(event?: any): Promise { - const instance = this.terminalService.getActiveInstance(); - if (instance) { - instance.dispose(true); - if (this.terminalService.terminalInstances.length > 0) { - this.terminalService.showPanel(true); + async run() { + await this._terminalService.doWithActiveInstance(async t => { + t.dispose(true); + if (this._terminalService.terminalInstances.length > 0) { + await this._terminalService.showPanel(true); } - } - return Promise.resolve(undefined); - } -} - -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); + }); } } @@ -148,194 +125,77 @@ export class QuickKillTerminalAction extends Action { export class CopyTerminalSelectionAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.COPY_SELECTION; - public static readonly LABEL = nls.localize('workbench.action.terminal.copySelection', "Copy Selection"); - public static readonly SHORT_LABEL = nls.localize('workbench.action.terminal.copySelection.short', "Copy"); + public static readonly LABEL = localize('workbench.action.terminal.copySelection', "Copy Selection"); + public static readonly SHORT_LABEL = localize('workbench.action.terminal.copySelection.short', "Copy"); constructor( id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label); } - public async run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - await terminalInstance.copySelection(); - } - return Promise.resolve(undefined); + async run() { + await this._terminalService.getActiveInstance()?.copySelection(); } } export class SelectAllTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.SELECT_ALL; - public static readonly LABEL = nls.localize('workbench.action.terminal.selectAll', "Select All"); + public static readonly LABEL = localize('workbench.action.terminal.selectAll', "Select All"); constructor( id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.selectAll(); - } - return Promise.resolve(undefined); - } -} - -export abstract class BaseSendTextTerminalAction extends Action { - constructor( - id: string, - label: string, - private _text: string, @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label); } - public run(event?: any): Promise { - const terminalInstance = this._terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.sendText(this._text, false); - } - return Promise.resolve(undefined); + async run() { + this._terminalService.getActiveInstance()?.selectAll(); } } -export class DeleteWordLeftTerminalAction extends BaseSendTextTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.DELETE_WORD_LEFT; - public static readonly LABEL = nls.localize('workbench.action.terminal.deleteWordLeft', "Delete Word Left"); - - constructor( - id: string, - label: string, - @ITerminalService terminalService: ITerminalService - ) { - // Send ctrl+W - super(id, label, String.fromCharCode('W'.charCodeAt(0) - 64), terminalService); - } -} - -export class DeleteWordRightTerminalAction extends BaseSendTextTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.DELETE_WORD_RIGHT; - public static readonly LABEL = nls.localize('workbench.action.terminal.deleteWordRight', "Delete Word Right"); - - constructor( - id: string, - label: string, - @ITerminalService terminalService: ITerminalService - ) { - // Send alt+d - super(id, label, '\x1bd', terminalService); - } -} - -export class DeleteToLineStartTerminalAction extends BaseSendTextTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.DELETE_TO_LINE_START; - public static readonly LABEL = nls.localize('workbench.action.terminal.deleteToLineStart', "Delete To Line Start"); - - constructor( - id: string, - label: string, - @ITerminalService terminalService: ITerminalService - ) { - // Send ctrl+u - super(id, label, '\u0015', terminalService); - } -} - -export class MoveToLineStartTerminalAction extends BaseSendTextTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.MOVE_TO_LINE_START; - public static readonly LABEL = nls.localize('workbench.action.terminal.moveToLineStart', "Move To Line Start"); - - constructor( - id: string, - label: string, - @ITerminalService terminalService: ITerminalService - ) { - // Send ctrl+A - super(id, label, String.fromCharCode('A'.charCodeAt(0) - 64), terminalService); - } -} - -export class MoveToLineEndTerminalAction extends BaseSendTextTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.MOVE_TO_LINE_END; - public static readonly LABEL = nls.localize('workbench.action.terminal.moveToLineEnd', "Move To Line End"); - - constructor( - id: string, - label: string, - @ITerminalService terminalService: ITerminalService - ) { - // Send ctrl+E - super(id, label, String.fromCharCode('E'.charCodeAt(0) - 64), terminalService); - } -} - -export class SendSequenceTerminalAction extends Action2 { - public static readonly ID = TERMINAL_COMMAND_ID.SEND_SEQUENCE; - public static readonly LABEL = nls.localize('workbench.action.terminal.sendSequence', "Send Custom Sequence To Terminal"); - - public run(accessor: ServicesAccessor, args: any): void { - const terminalInstance = accessor.get(ITerminalService).getActiveInstance(); - if (!terminalInstance) { +export const terminalSendSequenceCommand = (accessor: ServicesAccessor, args: { text?: string } | undefined) => { + accessor.get(ITerminalService).doWithActiveInstance(t => { + if (!args?.text) { return; } - const configurationResolverService = accessor.get(IConfigurationResolverService); const workspaceContextService = accessor.get(IWorkspaceContextService); const historyService = accessor.get(IHistoryService); const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; const resolvedText = configurationResolverService.resolve(lastActiveWorkspaceRoot, args.text); - terminalInstance.sendText(resolvedText, false); - } -} + t.sendText(resolvedText, false); + }); +}; -export class CreateNewWithCwdTerminalAction extends Action2 { - public static readonly ID = TERMINAL_COMMAND_ID.NEW_WITH_CWD; - public static readonly LABEL = nls.localize('workbench.action.terminal.newWithCwd', "Create New Integrated Terminal Starting in a Custom Working Directory"); - public static readonly CWD_ARG_LABEL = nls.localize('workbench.action.terminal.newWithCwd.cwd', "The directory to start the terminal at"); - - public run(accessor: ServicesAccessor, args: { cwd: string } | undefined): Promise { - const terminalService = accessor.get(ITerminalService); - const instance = terminalService.createTerminal({ cwd: args?.cwd }); - if (!instance) { - return Promise.resolve(undefined); - } - terminalService.setActiveInstance(instance); - return terminalService.showPanel(true); - } -} export class CreateNewTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.NEW; - public static readonly LABEL = nls.localize('workbench.action.terminal.new', "Create New Integrated Terminal"); - public static readonly SHORT_LABEL = nls.localize('workbench.action.terminal.new.short', "New Terminal"); + public static readonly LABEL = localize('workbench.action.terminal.new', "Create New Integrated Terminal"); + public static readonly SHORT_LABEL = localize('workbench.action.terminal.new.short', "New Terminal"); constructor( id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService, - @ICommandService private readonly commandService: ICommandService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService + @ITerminalService private readonly _terminalService: ITerminalService, + @ICommandService private readonly _commandService: ICommandService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { super(id, label, 'terminal-action codicon-add'); } - public async run(event?: any): Promise { - const folders = this.workspaceContextService.getWorkspace().folders; + async run(event?: any) { + const folders = this._workspaceContextService.getWorkspace().folders; if (event instanceof MouseEvent && (event.altKey || event.ctrlKey)) { - const activeInstance = this.terminalService.getActiveInstance(); + const activeInstance = this._terminalService.getActiveInstance(); if (activeInstance) { - const cwd = await getCwdForSplit(this.terminalService.configHelper, activeInstance); - this.terminalService.splitInstance(activeInstance, { cwd }); - return undefined; + const cwd = await getCwdForSplit(this._terminalService.configHelper, activeInstance); + this._terminalService.splitInstance(activeInstance, { cwd }); + return; } } @@ -343,76 +203,54 @@ export class CreateNewTerminalAction extends Action { if (folders.length <= 1) { // Allow terminal service to handle the path when there is only a // single root - instance = this.terminalService.createTerminal(undefined); + instance = this._terminalService.createTerminal(undefined); } else { const options: IPickOptions = { - placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal") + placeHolder: localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal") }; - const workspace = await this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]); + const workspace = await this._commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]); if (!workspace) { // Don't create the instance if the workspace picker was canceled - return undefined; + return; } - instance = this.terminalService.createTerminal({ cwd: workspace.uri }); + instance = this._terminalService.createTerminal({ cwd: workspace.uri }); } - this.terminalService.setActiveInstance(instance); - return this.terminalService.showPanel(true); - } -} - -export class CreateNewInActiveWorkspaceTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.NEW_IN_ACTIVE_WORKSPACE; - public static readonly LABEL = nls.localize('workbench.action.terminal.newInActiveWorkspace', "Create New Integrated Terminal (In Active Workspace)"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const instance = this.terminalService.createTerminal(undefined); - if (!instance) { - return Promise.resolve(undefined); - } - this.terminalService.setActiveInstance(instance); - return this.terminalService.showPanel(true); + this._terminalService.setActiveInstance(instance); + await this._terminalService.showPanel(true); } } export class SplitTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.SPLIT; - public static readonly LABEL = nls.localize('workbench.action.terminal.split', "Split Terminal"); - public static readonly SHORT_LABEL = nls.localize('workbench.action.terminal.split.short', "Split"); + public static readonly LABEL = localize('workbench.action.terminal.split', "Split Terminal"); + public static readonly SHORT_LABEL = localize('workbench.action.terminal.split.short', "Split"); + public static readonly HORIZONTAL_CLASS = 'terminal-action codicon-split-horizontal'; + public static readonly VERTICAL_CLASS = 'terminal-action codicon-split-vertical'; constructor( id: string, label: string, @ITerminalService private readonly _terminalService: ITerminalService, - @ICommandService private readonly commandService: ICommandService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService + @ICommandService private readonly _commandService: ICommandService, + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { - super(id, label, 'terminal-action codicon-split-horizontal'); + super(id, label, SplitTerminalAction.HORIZONTAL_CLASS); } public async run(event?: any): Promise { - const instance = this._terminalService.getActiveInstance(); - if (!instance) { - return Promise.resolve(undefined); - } - const cwd = await getCwdForSplit(this._terminalService.configHelper, instance, this.workspaceContextService.getWorkspace().folders, this.commandService); - if (cwd === undefined) { - return undefined; - } - this._terminalService.splitInstance(instance, { cwd }); - return this._terminalService.showPanel(true); + await this._terminalService.doWithActiveInstance(async t => { + const cwd = await getCwdForSplit(this._terminalService.configHelper, t, this._workspaceContextService.getWorkspace().folders, this._commandService); + if (cwd === undefined) { + return undefined; + } + this._terminalService.splitInstance(t, { cwd }); + return this._terminalService.showPanel(true); + }); } } export class SplitInActiveWorkspaceTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.SPLIT_IN_ACTIVE_WORKSPACE; - public static readonly LABEL = nls.localize('workbench.action.terminal.splitInActiveWorkspace', "Split Terminal (In Active Workspace)"); + public static readonly LABEL = localize('workbench.action.terminal.splitInActiveWorkspace', "Split Terminal (In Active Workspace)"); constructor( id: string, label: string, @@ -421,208 +259,20 @@ export class SplitInActiveWorkspaceTerminalAction extends Action { super(id, label); } - public async run(event?: any): Promise { - const instance = this._terminalService.getActiveInstance(); - if (!instance) { - return Promise.resolve(undefined); - } - const cwd = await getCwdForSplit(this._terminalService.configHelper, instance); - this._terminalService.splitInstance(instance, { cwd }); - return this._terminalService.showPanel(true); - } -} - -export class FocusPreviousPaneTerminalAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.FOCUS_PREVIOUS_PANE; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusPreviousPane', "Focus Previous Pane"); - - constructor( - id: string, label: string, - @ITerminalService private readonly _terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const tab = this._terminalService.getActiveTab(); - if (!tab) { - return Promise.resolve(undefined); - } - tab.focusPreviousPane(); - return this._terminalService.showPanel(true); - } -} - -export class FocusNextPaneTerminalAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.FOCUS_NEXT_PANE; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusNextPane', "Focus Next Pane"); - - constructor( - id: string, label: string, - @ITerminalService private readonly _terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const tab = this._terminalService.getActiveTab(); - if (!tab) { - return Promise.resolve(undefined); - } - tab.focusNextPane(); - return this._terminalService.showPanel(true); - } -} - -export abstract class BaseFocusDirectionTerminalAction extends Action { - constructor( - id: string, label: string, - private _direction: Direction, - @ITerminalService private readonly _terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const tab = this._terminalService.getActiveTab(); - if (tab) { - tab.resizePane(this._direction); - } - return Promise.resolve(undefined); - } -} - -export class ResizePaneLeftTerminalAction extends BaseFocusDirectionTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.RESIZE_PANE_LEFT; - public static readonly LABEL = nls.localize('workbench.action.terminal.resizePaneLeft', "Resize Pane Left"); - - constructor( - id: string, label: string, - @ITerminalService readonly terminalService: ITerminalService - ) { - super(id, label, Direction.Left, terminalService); - } -} - -export class ResizePaneRightTerminalAction extends BaseFocusDirectionTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.RESIZE_PANE_RIGHT; - public static readonly LABEL = nls.localize('workbench.action.terminal.resizePaneRight', "Resize Pane Right"); - - constructor( - id: string, label: string, - @ITerminalService readonly terminalService: ITerminalService - ) { - super(id, label, Direction.Right, terminalService); - } -} - -export class ResizePaneUpTerminalAction extends BaseFocusDirectionTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.RESIZE_PANE_UP; - public static readonly LABEL = nls.localize('workbench.action.terminal.resizePaneUp', "Resize Pane Up"); - - constructor( - id: string, label: string, - @ITerminalService readonly terminalService: ITerminalService - ) { - super(id, label, Direction.Up, terminalService); - } -} - -export class ResizePaneDownTerminalAction extends BaseFocusDirectionTerminalAction { - public static readonly ID = TERMINAL_COMMAND_ID.RESIZE_PANE_DOWN; - public static readonly LABEL = nls.localize('workbench.action.terminal.resizePaneDown', "Resize Pane Down"); - - constructor( - id: string, label: string, - @ITerminalService readonly terminalService: ITerminalService - ) { - super(id, label, Direction.Down, terminalService); - } -} - -export class FocusActiveTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.FOCUS; - public static readonly LABEL = nls.localize('workbench.action.terminal.focus', "Focus Terminal"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const instance = this.terminalService.getActiveOrCreateInstance(); - if (!instance) { - return Promise.resolve(undefined); - } - this.terminalService.setActiveInstance(instance); - return this.terminalService.showPanel(true); - } -} - -export class FocusNextTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.FOCUS_NEXT; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusNext', "Focus Next Terminal"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - this.terminalService.setActiveTabToNext(); - return this.terminalService.showPanel(true); - } -} - -export class FocusPreviousTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.FOCUS_PREVIOUS; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusPrevious', "Focus Previous Terminal"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - this.terminalService.setActiveTabToPrevious(); - return this.terminalService.showPanel(true); + async run() { + await this._terminalService.doWithActiveInstance(async t => { + const cwd = await getCwdForSplit(this._terminalService.configHelper, t); + this._terminalService.splitInstance(t, { cwd }); + await this._terminalService.showPanel(true); + }); } } export class TerminalPasteAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.PASTE; - public static readonly LABEL = nls.localize('workbench.action.terminal.paste', "Paste into Active Terminal"); - public static readonly SHORT_LABEL = nls.localize('workbench.action.terminal.paste.short', "Paste"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public async run(event?: any): Promise { - const instance = this.terminalService.getActiveOrCreateInstance(); - if (instance) { - await instance.paste(); - } - } -} - -export class SelectDefaultShellWindowsTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.SELECT_DEFAULT_SHELL; - public static readonly LABEL = nls.localize('workbench.action.terminal.selectDefaultShell', "Select Default Shell"); + public static readonly LABEL = localize('workbench.action.terminal.paste', "Paste into Active Terminal"); + public static readonly SHORT_LABEL = localize('workbench.action.terminal.paste.short', "Paste"); constructor( id: string, label: string, @@ -631,89 +281,36 @@ export class SelectDefaultShellWindowsTerminalAction extends Action { super(id, label); } - public run(event?: any): Promise { - return this._terminalService.selectDefaultWindowsShell(); + async run() { + this._terminalService.getActiveOrCreateInstance()?.paste(); } } -export class RunSelectedTextInTerminalAction extends Action { +export class SelectDefaultShellWindowsTerminalAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.RUN_SELECTED_TEXT; - public static readonly LABEL = nls.localize('workbench.action.terminal.runSelectedText', "Run Selected Text In Active Terminal"); + public static readonly ID = TERMINAL_COMMAND_ID.SELECT_DEFAULT_SHELL; + public static readonly LABEL = localize('workbench.action.terminal.selectDefaultShell', "Select Default Shell"); constructor( id: string, label: string, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @ITerminalService private readonly terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label); } - public run(event?: any): Promise { - const instance = this.terminalService.getActiveOrCreateInstance(); - if (!instance) { - return Promise.resolve(undefined); - } - let editor = this.codeEditorService.getFocusedCodeEditor(); - if (!editor || !editor.hasModel()) { - return Promise.resolve(undefined); - } - let selection = editor.getSelection(); - let text: string; - if (selection.isEmpty()) { - text = editor.getModel().getLineContent(selection.selectionStartLineNumber).trim(); - } else { - const endOfLinePreference = isWindows ? EndOfLinePreference.LF : EndOfLinePreference.CRLF; - text = editor.getModel().getValueInRange(selection, endOfLinePreference); - } - instance.sendText(text, true); - return this.terminalService.showPanel(); - } -} - -export class RunActiveFileInTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.RUN_ACTIVE_FILE; - public static readonly LABEL = nls.localize('workbench.action.terminal.runActiveFile', "Run Active File In Active Terminal"); - - constructor( - id: string, label: string, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @ITerminalService private readonly terminalService: ITerminalService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(id, label); - } - - public async run(event?: any): Promise { - const instance = this.terminalService.getActiveOrCreateInstance(); - if (!instance) { - return Promise.resolve(undefined); - } - const editor = this.codeEditorService.getActiveCodeEditor(); - if (!editor || !editor.hasModel()) { - return Promise.resolve(undefined); - } - const uri = editor.getModel().uri; - if (uri.scheme !== 'file') { - this.notificationService.warn(nls.localize('workbench.action.terminal.runActiveFile.noFile', 'Only files on disk can be run in the terminal')); - return Promise.resolve(undefined); - } - - const path = await this.terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title, instance.shellType); - instance.sendText(path, true); - return this.terminalService.showPanel(); + async run() { + this._terminalService.selectDefaultShell(); } } export class SwitchTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.SWITCH_TERMINAL; - public static readonly LABEL = nls.localize('workbench.action.terminal.switchTerminal', "Switch Terminal"); + public static readonly LABEL = localize('workbench.action.terminal.switchTerminal', "Switch Terminal"); constructor( id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label, 'terminal-action switch-terminal'); } @@ -723,16 +320,16 @@ export class SwitchTerminalAction extends Action { return Promise.resolve(null); } if (item === SwitchTerminalActionViewItem.SEPARATOR) { - this.terminalService.refreshActiveTab(); + this._terminalService.refreshActiveTab(); return Promise.resolve(null); } if (item === SelectDefaultShellWindowsTerminalAction.LABEL) { - this.terminalService.refreshActiveTab(); - return this.terminalService.selectDefaultWindowsShell(); + this._terminalService.refreshActiveTab(); + return this._terminalService.selectDefaultShell(); } const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1; - this.terminalService.setActiveTabByIndex(selectedTabIndex); - return this.terminalService.showPanel(true); + this._terminalService.setActiveTabByIndex(selectedTabIndex); + return this._terminalService.showPanel(true); } } @@ -742,647 +339,932 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { constructor( action: IAction, - @ITerminalService private readonly terminalService: ITerminalService, - @IThemeService themeService: IThemeService, + @ITerminalService private readonly _terminalService: ITerminalService, + @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: localize('terminals', 'Open Terminals.') }); - this._register(terminalService.onInstancesChanged(this._updateItems, this)); - this._register(terminalService.onActiveTabChanged(this._updateItems, this)); - this._register(terminalService.onInstanceTitleChanged(this._updateItems, this)); - this._register(terminalService.onTabDisposed(this._updateItems, this)); - this._register(attachSelectBoxStyler(this.selectBox, themeService)); + this._register(_terminalService.onInstancesChanged(this._updateItems, this)); + this._register(_terminalService.onActiveTabChanged(this._updateItems, this)); + this._register(_terminalService.onInstanceTitleChanged(this._updateItems, this)); + this._register(_terminalService.onTabDisposed(this._updateItems, this)); + this._register(attachSelectBoxStyler(this.selectBox, _themeService)); + } + + render(container: HTMLElement): void { + super.render(container); + addClass(container, 'switch-terminal'); + this._register(attachStylerCallback(this._themeService, { selectBorder }, colors => { + container.style.borderColor = colors.selectBorder ? `${colors.selectBorder}` : ''; + })); } 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); + this.setOptions(getTerminalSelectOpenItems(this._terminalService), this._terminalService.activeTabIndex); } } -export class ScrollDownTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.SCROLL_DOWN_LINE; - public static readonly LABEL = nls.localize('workbench.action.terminal.scrollDown', "Scroll Down (Line)"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.scrollDownLine(); - } - return Promise.resolve(undefined); - } -} - -export class ScrollDownPageTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.SCROLL_DOWN_PAGE; - public static readonly LABEL = nls.localize('workbench.action.terminal.scrollDownPage', "Scroll Down (Page)"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.scrollDownPage(); - } - return Promise.resolve(undefined); - } -} - -export class ScrollToBottomTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.SCROLL_TO_BOTTOM; - public static readonly LABEL = nls.localize('workbench.action.terminal.scrollToBottom', "Scroll to Bottom"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.scrollToBottom(); - } - return Promise.resolve(undefined); - } -} - -export class ScrollUpTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.SCROLL_UP_LINE; - public static readonly LABEL = nls.localize('workbench.action.terminal.scrollUp', "Scroll Up (Line)"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.scrollUpLine(); - } - return Promise.resolve(undefined); - } -} - -export class ScrollUpPageTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.SCROLL_UP_PAGE; - public static readonly LABEL = nls.localize('workbench.action.terminal.scrollUpPage', "Scroll Up (Page)"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.scrollUpPage(); - } - return Promise.resolve(undefined); - } -} - -export class ScrollToTopTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.SCROLL_TO_TOP; - public static readonly LABEL = nls.localize('workbench.action.terminal.scrollToTop', "Scroll to Top"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.scrollToTop(); - } - return Promise.resolve(undefined); - } -} - -export class NavigationModeExitTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.NAVIGATION_MODE_EXIT; - public static readonly LABEL = nls.localize('workbench.action.terminal.navigationModeExit', "Exit Navigation Mode"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance && terminalInstance.navigationMode) { - terminalInstance.navigationMode.exitNavigationMode(); - } - return Promise.resolve(undefined); - } -} - - - -export class NavigationModeFocusPreviousTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.NAVIGATION_MODE_FOCUS_PREVIOUS; - public static readonly LABEL = nls.localize('workbench.action.terminal.navigationModeFocusPrevious', "Focus Previous Line (Navigation Mode)"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance && terminalInstance.navigationMode) { - terminalInstance.navigationMode.focusPreviousLine(); - } - return Promise.resolve(undefined); - } -} - -export class NavigationModeFocusNextTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.NAVIGATION_MODE_FOCUS_NEXT; - public static readonly LABEL = nls.localize('workbench.action.terminal.navigationModeFocusNext', "Focus Next Line (Navigation Mode)"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance && terminalInstance.navigationMode) { - terminalInstance.navigationMode.focusNextLine(); - } - return Promise.resolve(undefined); - } +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 ClearTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.CLEAR; - public static readonly LABEL = nls.localize('workbench.action.terminal.clear', "Clear"); + public static readonly LABEL = localize('workbench.action.terminal.clear', "Clear"); constructor( id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label); } - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance) { - terminalInstance.clear(); - } - return Promise.resolve(undefined); - } -} - -export class ClearSelectionTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.CLEAR_SELECTION; - public static readonly LABEL = nls.localize('workbench.action.terminal.clearSelection', "Clear Selection"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const terminalInstance = this.terminalService.getActiveInstance(); - if (terminalInstance && terminalInstance.hasSelection()) { - terminalInstance.clearSelection(); - } - return Promise.resolve(undefined); - } -} - -export class ManageWorkspaceShellPermissionsTerminalCommand extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.MANAGE_WORKSPACE_SHELL_PERMISSIONS; - public static readonly LABEL = nls.localize('workbench.action.terminal.manageWorkspaceShellPermissions', "Manage Workspace Shell Permissions"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public async run(event?: any): Promise { - await this.terminalService.manageWorkspaceShellPermissions(); - } -} - -export class RenameTerminalAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.RENAME; - public static readonly LABEL = nls.localize('workbench.action.terminal.rename', "Rename"); - - 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(); - if (!terminalInstance) { - return Promise.resolve(undefined); - } - const name = await this.quickInputService.input({ - value: terminalInstance.title, - prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"), + async run() { + this._terminalService.doWithActiveInstance(t => { + t.clear(); + t.focus(); }); - if (name) { - terminalInstance.setTitle(name, TitleEventSource.Api); + } +} + +export function registerTerminalActions() { + const category = TERMINAL_ACTION_CATEGORY; + + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.NEW_IN_ACTIVE_WORKSPACE, + title: localize('workbench.action.terminal.newInActiveWorkspace', "Create New Integrated Terminal (In Active Workspace)"), + f1: true, + category + }); } - } -} -export class RenameWithArgTerminalAction extends Action2 { - public static readonly ID = TERMINAL_COMMAND_ID.RENAME_WITH_ARG; - public static readonly LABEL = nls.localize('workbench.action.terminal.renameWithArg', "Rename the Currently Active Terminal"); - public static readonly NAME_ARG_LABEL = nls.localize('workbench.action.terminal.renameWithArg.name', "The new name for the terminal"); - - public run( - accessor: ServicesAccessor, - args?: { name?: string } - ): void { - const notificationService = accessor.get(INotificationService); - const terminalInstance = accessor.get(ITerminalService).getActiveInstance(); - - if (!terminalInstance) { - notificationService.warn(nls.localize('workbench.action.terminal.renameWithArg.noTerminal', "No active terminal to rename")); - return; + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + const instance = terminalService.createTerminal(undefined); + if (!instance) { + return; + } + terminalService.setActiveInstance(instance); + await terminalService.showPanel(true); } - - if (!args || !args.name) { - notificationService.warn(nls.localize('workbench.action.terminal.renameWithArg.noName', "No name argument provided")); - return; + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FOCUS_PREVIOUS_PANE, + title: localize('workbench.action.terminal.focusPreviousPane', "Focus Previous Pane"), + f1: true, + category, + keybinding: { + primary: KeyMod.Alt | KeyCode.LeftArrow, + secondary: [KeyMod.Alt | KeyCode.UpArrow], + mac: { + primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.LeftArrow, + secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.UpArrow] + }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); } - - terminalInstance.setTitle(args.name, TitleEventSource.Api); - } -} - -export class FocusTerminalFindWidgetAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.FIND_WIDGET_FOCUS; - public static readonly LABEL = nls.localize('workbench.action.terminal.focusFindWidget', "Focus Find Widget"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - return this.terminalService.focusFindWidget(); - } -} - -export class HideTerminalFindWidgetAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.FIND_WIDGET_HIDE; - public static readonly LABEL = nls.localize('workbench.action.terminal.hideFindWidget', "Hide Find Widget"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - return Promise.resolve(this.terminalService.hideFindWidget()); - } -} - -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)); + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + terminalService.getActiveTab()?.focusPreviousPane(); + await terminalService.showPanel(true); } - return actions; - } - - public hasActions(context: any): boolean { - return true; - } -} - -export class QuickOpenTermAction extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.QUICK_OPEN_TERM; - public static readonly LABEL = nls.localize('quickOpenTerm', "Switch Active Terminal"); - - constructor( - id: string, - label: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService - ) { - 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); - } -} - -export class ScrollToPreviousCommandAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.SCROLL_TO_PREVIOUS_COMMAND; - public static readonly LABEL = nls.localize('workbench.action.terminal.scrollToPreviousCommand', "Scroll To Previous Command"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - const instance = this.terminalService.getActiveInstance(); - if (instance && instance.commandTracker) { - instance.commandTracker.scrollToPreviousCommand(); - instance.focus(); + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FOCUS_NEXT_PANE, + title: localize('workbench.action.terminal.focusNextPane', "Focus Next Pane"), + f1: true, + category, + keybinding: { + primary: KeyMod.Alt | KeyCode.RightArrow, + secondary: [KeyMod.Alt | KeyCode.DownArrow], + mac: { + primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.RightArrow, + secondary: [KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.DownArrow] + }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); } - return Promise.resolve(undefined); - } -} - -export class ScrollToNextCommandAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.SCROLL_TO_NEXT_COMMAND; - public static readonly LABEL = nls.localize('workbench.action.terminal.scrollToNextCommand', "Scroll To Next Command"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - const instance = this.terminalService.getActiveInstance(); - if (instance && instance.commandTracker) { - instance.commandTracker.scrollToNextCommand(); - instance.focus(); + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + terminalService.getActiveTab()?.focusNextPane(); + await terminalService.showPanel(true); } - return Promise.resolve(undefined); - } -} - -export class SelectToPreviousCommandAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_COMMAND; - public static readonly LABEL = nls.localize('workbench.action.terminal.selectToPreviousCommand', "Select To Previous Command"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - const instance = this.terminalService.getActiveInstance(); - if (instance && instance.commandTracker) { - instance.commandTracker.selectToPreviousCommand(); - instance.focus(); + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.RESIZE_PANE_LEFT, + title: localize('workbench.action.terminal.resizePaneLeft', "Resize Pane Left"), + f1: true, + category, + keybinding: { + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow }, + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); } - return Promise.resolve(undefined); - } -} - -export class SelectToNextCommandAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.SELECT_TO_NEXT_COMMAND; - public static readonly LABEL = nls.localize('workbench.action.terminal.selectToNextCommand', "Select To Next Command"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - const instance = this.terminalService.getActiveInstance(); - if (instance && instance.commandTracker) { - instance.commandTracker.selectToNextCommand(); - instance.focus(); + async run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveTab()?.resizePane(Direction.Left); } - return Promise.resolve(undefined); - } -} - -export class SelectToPreviousLineAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_LINE; - public static readonly LABEL = nls.localize('workbench.action.terminal.selectToPreviousLine', "Select To Previous Line"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - const instance = this.terminalService.getActiveInstance(); - if (instance && instance.commandTracker) { - instance.commandTracker.selectToPreviousLine(); - instance.focus(); + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.RESIZE_PANE_RIGHT, + title: localize('workbench.action.terminal.resizePaneRight', "Resize Pane Right"), + f1: true, + category, + keybinding: { + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow }, + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); } - return Promise.resolve(undefined); - } -} - -export class SelectToNextLineAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.SELECT_TO_NEXT_LINE; - public static readonly LABEL = nls.localize('workbench.action.terminal.selectToNextLine', "Select To Next Line"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - const instance = this.terminalService.getActiveInstance(); - if (instance && instance.commandTracker) { - instance.commandTracker.selectToNextLine(); - instance.focus(); + async run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveTab()?.resizePane(Direction.Right); } - return Promise.resolve(undefined); - } -} - - -export class ToggleEscapeSequenceLoggingAction extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_ESCAPE_SEQUENCE_LOGGING; - public static readonly LABEL = nls.localize('workbench.action.terminal.toggleEscapeSequenceLogging', "Toggle Escape Sequence Logging"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - const instance = this.terminalService.getActiveInstance(); - if (instance) { - instance.toggleEscapeSequenceLogging(); + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.RESIZE_PANE_UP, + title: localize('workbench.action.terminal.resizePaneUp', "Resize Pane Up"), + f1: true, + category, + keybinding: { + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); } - return Promise.resolve(undefined); - } -} - -abstract class ToggleFindOptionCommand extends Action { - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - protected abstract runInner(state: FindReplaceState): void; - - public run(): Promise { - const state = this.terminalService.getFindState(); - this.runInner(state); - return Promise.resolve(undefined); - } -} - -export class ToggleRegexCommand extends ToggleFindOptionCommand { - public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX; - public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindRegex', "Toggle find using regex"); - - protected runInner(state: FindReplaceState): void { - state.change({ isRegex: !state.isRegex }, false); - } -} - -export class ToggleWholeWordCommand extends ToggleFindOptionCommand { - public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD; - public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindWholeWord', "Toggle find using whole word"); - - protected runInner(state: FindReplaceState): void { - state.change({ wholeWord: !state.wholeWord }, false); - } -} - -export class ToggleCaseSensitiveCommand extends ToggleFindOptionCommand { - public static readonly ID = TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE; - public static readonly LABEL = nls.localize('workbench.action.terminal.toggleFindCaseSensitive', "Toggle find using case sensitive"); - - protected runInner(state: FindReplaceState): void { - state.change({ matchCase: !state.matchCase }, false); - } -} - -export class FindNext extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.FIND_NEXT; - public static readonly LABEL = nls.localize('workbench.action.terminal.findNext', "Find next"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - this.terminalService.findNext(); - return Promise.resolve(undefined); - } -} - -export class FindPrevious extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.FIND_PREVIOUS; - public static readonly LABEL = nls.localize('workbench.action.terminal.findPrevious', "Find previous"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(): Promise { - this.terminalService.findPrevious(); - return Promise.resolve(undefined); - } + async run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveTab()?.resizePane(Direction.Up); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.RESIZE_PANE_DOWN, + title: localize('workbench.action.terminal.resizePaneDown', "Resize Pane Down"), + f1: true, + category, + keybinding: { + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + async run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveTab()?.resizePane(Direction.Down); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FOCUS, + title: localize('workbench.action.terminal.focus', "Focus Terminal"), + f1: true, + category + }); + } + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + const instance = terminalService.getActiveOrCreateInstance(); + if (!instance) { + return; + } + terminalService.setActiveInstance(instance); + return terminalService.showPanel(true); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FOCUS_NEXT, + title: localize('workbench.action.terminal.focusNext', "Focus Next Terminal"), + f1: true, + category + }); + } + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + terminalService.setActiveTabToNext(); + await terminalService.showPanel(true); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FOCUS_PREVIOUS, + title: localize('workbench.action.terminal.focusPrevious', "Focus Previous Terminal"), + f1: true, + category + }); + } + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + terminalService.setActiveTabToPrevious(); + await terminalService.showPanel(true); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.RUN_SELECTED_TEXT, + title: localize('workbench.action.terminal.runSelectedText', "Run Selected Text In Active Terminal"), + f1: true, + category + }); + } + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + const codeEditorService = accessor.get(ICodeEditorService); + + const instance = terminalService.getActiveOrCreateInstance(); + let editor = codeEditorService.getFocusedCodeEditor(); + if (!editor || !editor.hasModel()) { + return; + } + let selection = editor.getSelection(); + let text: string; + if (selection.isEmpty()) { + text = editor.getModel().getLineContent(selection.selectionStartLineNumber).trim(); + } else { + const endOfLinePreference = isWindows ? EndOfLinePreference.LF : EndOfLinePreference.CRLF; + text = editor.getModel().getValueInRange(selection, endOfLinePreference); + } + instance.sendText(text, true); + return terminalService.showPanel(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.RUN_ACTIVE_FILE, + title: localize('workbench.action.terminal.runActiveFile', "Run Active File In Active Terminal"), + f1: true, + category + }); + } + async run(accessor: ServicesAccessor) { + const terminalService = accessor.get(ITerminalService); + const codeEditorService = accessor.get(ICodeEditorService); + const notificationService = accessor.get(INotificationService); + + const instance = terminalService.getActiveOrCreateInstance(); + await instance.processReady; + + const editor = codeEditorService.getActiveCodeEditor(); + if (!editor || !editor.hasModel()) { + return; + } + + const uri = editor.getModel().uri; + if (uri.scheme !== 'file') { + notificationService.warn(localize('workbench.action.terminal.runActiveFile.noFile', 'Only files on disk can be run in the terminal')); + return; + } + + // TODO: Convert this to ctrl+c, ctrl+v for pwsh? + const path = await terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title, instance.shellType); + instance.sendText(path, true); + return terminalService.showPanel(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SCROLL_DOWN_LINE, + title: localize('workbench.action.terminal.scrollDown', "Scroll Down (Line)"), + f1: true, + category, + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.scrollDownLine(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SCROLL_DOWN_PAGE, + title: localize('workbench.action.terminal.scrollDownPage', "Scroll Down (Page)"), + f1: true, + category, + keybinding: { + primary: KeyMod.Shift | KeyCode.PageDown, + mac: { primary: KeyCode.PageDown }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.scrollDownPage(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SCROLL_TO_BOTTOM, + title: localize('workbench.action.terminal.scrollToBottom', "Scroll to Bottom"), + f1: true, + category, + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.End, + linux: { primary: KeyMod.Shift | KeyCode.End }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.scrollToBottom(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SCROLL_UP_LINE, + title: localize('workbench.action.terminal.scrollUp', "Scroll Up (Line)"), + f1: true, + category, + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.scrollUpLine(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SCROLL_UP_PAGE, + title: localize('workbench.action.terminal.scrollUpPage', "Scroll Up (Page)"), + f1: true, + category, + keybinding: { + primary: KeyMod.Shift | KeyCode.PageUp, + mac: { primary: KeyCode.PageUp }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.scrollUpPage(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SCROLL_TO_TOP, + title: localize('workbench.action.terminal.scrollToTop', "Scroll to Top"), + f1: true, + category, + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.Home, + linux: { primary: KeyMod.Shift | KeyCode.Home }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.scrollToTop(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.NAVIGATION_MODE_EXIT, + title: localize('workbench.action.terminal.navigationModeExit', "Exit Navigation Mode"), + f1: true, + category, + keybinding: { + primary: KeyCode.Escape, + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.navigationMode?.focusPreviousLine(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.NAVIGATION_MODE_FOCUS_PREVIOUS, + title: localize('workbench.action.terminal.navigationModeFocusPrevious', "Focus Previous Line (Navigation Mode)"), + f1: true, + category, + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + when: ContextKeyExpr.or( + ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED) + ), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.navigationMode?.focusPreviousLine(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.NAVIGATION_MODE_FOCUS_NEXT, + title: localize('workbench.action.terminal.navigationModeFocusNext', "Focus Next Line (Navigation Mode)"), + f1: true, + category, + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + when: ContextKeyExpr.or( + ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED) + ), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.navigationMode?.focusNextLine(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.CLEAR_SELECTION, + title: localize('workbench.action.terminal.clearSelection', "Clear Selection"), + f1: true, + category, + keybinding: { + primary: KeyCode.Escape, + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + const terminalInstance = accessor.get(ITerminalService).getActiveInstance(); + if (terminalInstance && terminalInstance.hasSelection()) { + terminalInstance.clearSelection(); + } + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.MANAGE_WORKSPACE_SHELL_PERMISSIONS, + title: localize('workbench.action.terminal.manageWorkspaceShellPermissions', "Manage Workspace Shell Permissions"), + f1: true, + category + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).manageWorkspaceShellPermissions(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.RENAME, + title: localize('workbench.action.terminal.rename', "Rename"), + f1: true, + category + }); + } + async run(accessor: ServicesAccessor) { + await accessor.get(ITerminalService).doWithActiveInstance(async t => { + const name = await accessor.get(IQuickInputService).input({ + value: t.title, + prompt: localize('workbench.action.terminal.rename.prompt', "Enter terminal name"), + }); + if (name) { + t.setTitle(name, TitleEventSource.Api); + } + }); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FIND_WIDGET_FOCUS, + title: localize('workbench.action.terminal.focusFindWidget', "Focus Find Widget"), + f1: true, + category, + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KEY_F, + when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FOCUS), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).focusFindWidget(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FIND_WIDGET_HIDE, + title: localize('workbench.action.terminal.hideFindWidget', "Hide Find Widget"), + f1: true, + category, + keybinding: { + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).hideFindWidget(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.QUICK_OPEN_TERM, + title: localize('quickAccessTerminal', "Switch Active Terminal"), + f1: true, + category + }); + } + run(accessor: ServicesAccessor) { + accessor.get(IQuickInputService).quickAccess.show(TerminalQuickAccessProvider.PREFIX); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SCROLL_TO_PREVIOUS_COMMAND, + title: localize('workbench.action.terminal.scrollToPreviousCommand', "Scroll To Previous Command"), + f1: true, + category, + keybinding: { + mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).doWithActiveInstance(t => { + t.commandTracker?.scrollToPreviousCommand(); + t.focus(); + }); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SCROLL_TO_NEXT_COMMAND, + title: localize('workbench.action.terminal.scrollToNextCommand', "Scroll To Next Command"), + f1: true, + category, + keybinding: { + mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, + when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).doWithActiveInstance(t => { + t.commandTracker?.scrollToNextCommand(); + t.focus(); + }); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_COMMAND, + title: localize('workbench.action.terminal.selectToPreviousCommand', "Select To Previous Command"), + f1: true, + category, + keybinding: { + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).doWithActiveInstance(t => { + t.commandTracker?.selectToPreviousCommand(); + t.focus(); + }); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SELECT_TO_NEXT_COMMAND, + title: localize('workbench.action.terminal.selectToNextCommand', "Select To Next Command"), + f1: true, + category, + keybinding: { + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }, + when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).doWithActiveInstance(t => { + t.commandTracker?.selectToNextCommand(); + t.focus(); + }); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_LINE, + title: localize('workbench.action.terminal.selectToPreviousLine', "Select To Previous Line"), + f1: true, + category + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).doWithActiveInstance(t => { + t.commandTracker?.selectToPreviousLine(); + t.focus(); + }); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.SELECT_TO_NEXT_LINE, + title: localize('workbench.action.terminal.selectToNextLine', "Select To Next Line"), + f1: true, + category + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).doWithActiveInstance(t => { + t.commandTracker?.selectToNextLine(); + t.focus(); + }); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.TOGGLE_ESCAPE_SEQUENCE_LOGGING, + title: localize('workbench.action.terminal.toggleEscapeSequenceLogging', "Toggle Escape Sequence Logging"), + f1: true, + category + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).getActiveInstance()?.toggleEscapeSequenceLogging(); + } + }); + registerAction2(class extends Action2 { + constructor() { + const title = localize('workbench.action.terminal.sendSequence', "Send Custom Sequence To Terminal"); + super({ + id: TERMINAL_COMMAND_ID.SEND_SEQUENCE, + title, + category, + description: { + description: title, + args: [{ + name: 'args', + schema: { + type: 'object', + required: ['text'], + properties: { + text: { type: 'string' } + }, + } + }] + } + }); + } + run(accessor: ServicesAccessor, args?: { text?: string }) { + terminalSendSequenceCommand(accessor, args); + } + }); + registerAction2(class extends Action2 { + constructor() { + const title = localize('workbench.action.terminal.newWithCwd', "Create New Integrated Terminal Starting in a Custom Working Directory"); + super({ + id: TERMINAL_COMMAND_ID.NEW_WITH_CWD, + title, + category, + description: { + description: title, + args: [{ + name: 'args', + schema: { + type: 'object', + required: ['cwd'], + properties: { + cwd: { + description: localize('workbench.action.terminal.newWithCwd.cwd', "The directory to start the terminal at"), + type: 'string' + } + }, + } + }] + } + }); + } + async run(accessor: ServicesAccessor, args?: { cwd?: string }) { + const terminalService = accessor.get(ITerminalService); + const instance = terminalService.createTerminal({ cwd: args?.cwd }); + if (!instance) { + return; + } + terminalService.setActiveInstance(instance); + return terminalService.showPanel(true); + } + }); + registerAction2(class extends Action2 { + constructor() { + const title = localize('workbench.action.terminal.renameWithArg', "Rename the Currently Active Terminal"); + super({ + id: TERMINAL_COMMAND_ID.RENAME_WITH_ARG, + title, + category, + description: { + description: title, + args: [{ + name: 'args', + schema: { + type: 'object', + required: ['name'], + properties: { + name: { + description: localize('workbench.action.terminal.renameWithArg.name', "The new name for the terminal"), + type: 'string', + minLength: 1 + } + } + } + }] + } + }); + } + run(accessor: ServicesAccessor, args?: { name?: string }) { + const notificationService = accessor.get(INotificationService); + if (!args?.name) { + notificationService.warn(localize('workbench.action.terminal.renameWithArg.noName', "No name argument provided")); + return; + } + accessor.get(ITerminalService).getActiveInstance()?.setTitle(args.name, TitleEventSource.Api); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX, + title: localize('workbench.action.terminal.toggleFindRegex', "Toggle find using regex"), + f1: true, + category, + keybinding: { + primary: KeyMod.Alt | KeyCode.KEY_R, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R }, + when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + const state = accessor.get(ITerminalService).getFindState(); + state.change({ isRegex: !state.isRegex }, false); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD, + title: localize('workbench.action.terminal.toggleFindWholeWord', "Toggle find using whole word"), + f1: true, + category, + keybinding: { + primary: KeyMod.Alt | KeyCode.KEY_W, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W }, + when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), + weight: KeybindingWeight.WorkbenchContrib + }, + }); + } + run(accessor: ServicesAccessor) { + const state = accessor.get(ITerminalService).getFindState(); + state.change({ wholeWord: !state.wholeWord }, false); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE, + title: localize('workbench.action.terminal.toggleFindCaseSensitive', "Toggle find using case sensitive"), + f1: true, + category, + keybinding: { + primary: KeyMod.Alt | KeyCode.KEY_C, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C }, + when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + run(accessor: ServicesAccessor) { + const state = accessor.get(ITerminalService).getFindState(); + state.change({ matchCase: !state.matchCase }, false); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FIND_NEXT, + title: localize('workbench.action.terminal.findNext', "Find next"), + f1: true, + category, + keybinding: [ + { + primary: KeyCode.F3, + mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] }, + when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), + weight: KeybindingWeight.WorkbenchContrib + }, + { + primary: KeyMod.Shift | KeyCode.Enter, + when: KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, + weight: KeybindingWeight.WorkbenchContrib + } + ] + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).findNext(); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TERMINAL_COMMAND_ID.FIND_PREVIOUS, + title: localize('workbench.action.terminal.findPrevious', "Find previous"), + f1: true, + category, + keybinding: [ + { + primary: KeyMod.Shift | KeyCode.F3, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3] }, + when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), + weight: KeybindingWeight.WorkbenchContrib + }, + { + primary: KeyCode.Enter, + when: KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, + weight: KeybindingWeight.WorkbenchContrib + } + ] + }); + } + run(accessor: ServicesAccessor) { + accessor.get(ITerminalService).findPrevious(); + } + }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index d182592bf01..883feed1c32 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 / lineHeight, + 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 cec072d72b3..8f22139ccb7 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 { 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, LEGACY_CONSOLE_MODE_EXIT_CODE, DEFAULT_COMMANDS_TO_SKIP_SHELL } 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,132 +40,13 @@ 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 const SLOW_CANVAS_RENDER_THRESHOLD = 50; const NUMBER_OF_FRAMES_TO_MEASURE = 20; -export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ - TERMINAL_COMMAND_ID.CLEAR_SELECTION, - TERMINAL_COMMAND_ID.CLEAR, - TERMINAL_COMMAND_ID.COPY_SELECTION, - TERMINAL_COMMAND_ID.DELETE_TO_LINE_START, - TERMINAL_COMMAND_ID.DELETE_WORD_LEFT, - TERMINAL_COMMAND_ID.DELETE_WORD_RIGHT, - TERMINAL_COMMAND_ID.FIND_WIDGET_FOCUS, - TERMINAL_COMMAND_ID.FIND_WIDGET_HIDE, - TERMINAL_COMMAND_ID.FIND_NEXT, - TERMINAL_COMMAND_ID.FIND_PREVIOUS, - TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX, - TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD, - TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE, - TERMINAL_COMMAND_ID.FOCUS_NEXT_PANE, - TERMINAL_COMMAND_ID.FOCUS_NEXT, - TERMINAL_COMMAND_ID.FOCUS_PREVIOUS_PANE, - TERMINAL_COMMAND_ID.FOCUS_PREVIOUS, - TERMINAL_COMMAND_ID.FOCUS, - TERMINAL_COMMAND_ID.KILL, - TERMINAL_COMMAND_ID.MOVE_TO_LINE_END, - TERMINAL_COMMAND_ID.MOVE_TO_LINE_START, - TERMINAL_COMMAND_ID.NEW_IN_ACTIVE_WORKSPACE, - TERMINAL_COMMAND_ID.NEW, - TERMINAL_COMMAND_ID.PASTE, - TERMINAL_COMMAND_ID.RESIZE_PANE_DOWN, - TERMINAL_COMMAND_ID.RESIZE_PANE_LEFT, - TERMINAL_COMMAND_ID.RESIZE_PANE_RIGHT, - TERMINAL_COMMAND_ID.RESIZE_PANE_UP, - TERMINAL_COMMAND_ID.RUN_ACTIVE_FILE, - TERMINAL_COMMAND_ID.RUN_SELECTED_TEXT, - TERMINAL_COMMAND_ID.SCROLL_DOWN_LINE, - TERMINAL_COMMAND_ID.SCROLL_DOWN_PAGE, - TERMINAL_COMMAND_ID.SCROLL_TO_BOTTOM, - TERMINAL_COMMAND_ID.SCROLL_TO_NEXT_COMMAND, - TERMINAL_COMMAND_ID.SCROLL_TO_PREVIOUS_COMMAND, - TERMINAL_COMMAND_ID.SCROLL_TO_TOP, - TERMINAL_COMMAND_ID.SCROLL_UP_LINE, - TERMINAL_COMMAND_ID.SCROLL_UP_PAGE, - TERMINAL_COMMAND_ID.SEND_SEQUENCE, - TERMINAL_COMMAND_ID.SELECT_ALL, - TERMINAL_COMMAND_ID.SELECT_TO_NEXT_COMMAND, - TERMINAL_COMMAND_ID.SELECT_TO_NEXT_LINE, - TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_COMMAND, - TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_LINE, - TERMINAL_COMMAND_ID.SPLIT_IN_ACTIVE_WORKSPACE, - TERMINAL_COMMAND_ID.SPLIT, - TERMINAL_COMMAND_ID.TOGGLE, - TERMINAL_COMMAND_ID.NAVIGATION_MODE_EXIT, - TERMINAL_COMMAND_ID.NAVIGATION_MODE_FOCUS_NEXT, - TERMINAL_COMMAND_ID.NAVIGATION_MODE_FOCUS_PREVIOUS, - 'editor.action.toggleTabFocusMode', - 'workbench.action.quickOpen', - 'workbench.action.quickOpenPreviousEditor', - 'workbench.action.showCommands', - 'workbench.action.tasks.build', - 'workbench.action.tasks.restartTask', - 'workbench.action.tasks.runTask', - 'workbench.action.tasks.reRunTask', - 'workbench.action.tasks.showLog', - 'workbench.action.tasks.showTasks', - 'workbench.action.tasks.terminate', - 'workbench.action.tasks.test', - 'workbench.action.toggleFullScreen', - 'workbench.action.terminal.focusAtIndex1', - 'workbench.action.terminal.focusAtIndex2', - 'workbench.action.terminal.focusAtIndex3', - 'workbench.action.terminal.focusAtIndex4', - 'workbench.action.terminal.focusAtIndex5', - 'workbench.action.terminal.focusAtIndex6', - 'workbench.action.terminal.focusAtIndex7', - 'workbench.action.terminal.focusAtIndex8', - 'workbench.action.terminal.focusAtIndex9', - 'workbench.action.focusSecondEditorGroup', - 'workbench.action.focusThirdEditorGroup', - 'workbench.action.focusFourthEditorGroup', - 'workbench.action.focusFifthEditorGroup', - 'workbench.action.focusSixthEditorGroup', - 'workbench.action.focusSeventhEditorGroup', - 'workbench.action.focusEighthEditorGroup', - 'workbench.action.nextPanelView', - 'workbench.action.previousPanelView', - 'workbench.action.nextSideBarView', - 'workbench.action.previousSideBarView', - 'workbench.action.debug.start', - 'workbench.action.debug.stop', - 'workbench.action.debug.run', - 'workbench.action.debug.restart', - 'workbench.action.debug.continue', - 'workbench.action.debug.pause', - 'workbench.action.debug.stepInto', - 'workbench.action.debug.stepOut', - 'workbench.action.debug.stepOver', - 'workbench.action.nextEditor', - 'workbench.action.previousEditor', - 'workbench.action.nextEditorInGroup', - 'workbench.action.previousEditorInGroup', - 'workbench.action.openNextRecentlyUsedEditor', - 'workbench.action.openPreviousRecentlyUsedEditor', - 'workbench.action.openNextRecentlyUsedEditorInGroup', - 'workbench.action.openPreviousRecentlyUsedEditorInGroup', - 'workbench.action.quickOpenPreviousRecentlyUsedEditor', - 'workbench.action.quickOpenLeastRecentlyUsedEditor', - 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup', - 'workbench.action.quickOpenLeastRecentlyUsedEditorInGroup', - 'workbench.action.focusActiveEditorGroup', - 'workbench.action.focusFirstEditorGroup', - 'workbench.action.focusLastEditorGroup', - 'workbench.action.firstEditorInGroup', - 'workbench.action.lastEditorInGroup', - 'workbench.action.navigateUp', - 'workbench.action.navigateDown', - 'workbench.action.navigateRight', - 'workbench.action.navigateLeft', - 'workbench.action.togglePanel', - 'workbench.action.quickOpenView', - 'workbench.action.toggleMaximizedPanel' -]; - let xtermConstructor: Promise | undefined; interface ICanvasDimensions { @@ -272,9 +153,12 @@ 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, + private readonly _terminalShellTypeContextKey: IContextKey, private readonly _configHelper: TerminalConfigHelper, private _container: HTMLElement | undefined, private _shellLaunchConfig: IShellLaunchConfig, @@ -290,6 +174,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(); @@ -485,7 +370,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { fastScrollSensitivity: editorOptions.fastScrollSensitivity, scrollSensitivity: editorOptions.mouseWheelScrollSensitivity, rendererType: config.rendererType === 'auto' || config.rendererType === 'experimentalWebgl' ? 'canvas' : config.rendererType, - wordSeparator: ' ()[]{}\',"`' + wordSeparator: config.wordSeparators }); this._xterm = xterm; this._xtermCore = (xterm as any)._core as XTermCore; @@ -523,11 +408,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 +489,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 && resolveResult.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape'; - if (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; @@ -648,13 +553,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { while (!dom.hasClass(currentElement, 'part')) { currentElement = currentElement.parentElement!; } - const hidePanelElement = currentElement.querySelector('.hide-panel-action'); - hidePanelElement.focus(); + const hidePanelElement = currentElement.querySelector('.hide-panel-action'); + hidePanelElement?.focus(); })); xtermHelper.insertBefore(focusTrap, xterm.textarea); this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => { this._terminalFocusContextKey.set(true); + if (this.shellType) { + this._terminalShellTypeContextKey.set(this.shellType.toString()); + } else { + this._terminalShellTypeContextKey.reset(); + } this._onFocused.fire(this); })); this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => { @@ -663,6 +573,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { })); this._register(dom.addDisposableListener(xterm.element, 'focus', () => { this._terminalFocusContextKey.set(true); + if (this.shellType) { + this._terminalShellTypeContextKey.set(this.shellType.toString()); + } else { + this._terminalShellTypeContextKey.reset(); + } })); this._register(dom.addDisposableListener(xterm.element, 'blur', () => { this._terminalFocusContextKey.reset(); @@ -1225,6 +1140,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._safeSetOption('macOptionIsMeta', config.macOptionIsMeta); this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection); this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord'); + this._safeSetOption('wordSeparator', config.wordSeparators); if (config.rendererType !== 'experimentalWebgl') { // Never set webgl as it's an addon not a rendererType this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType); @@ -1431,13 +1347,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 +1384,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 +1403,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..285f8c9b047 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -23,6 +23,8 @@ 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'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -59,6 +61,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 +90,9 @@ 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, + @IRemotePathService private readonly _remotePathService: IRemotePathService, + @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService ) { super(); this.ptyProcessReady = new Promise(c => { @@ -130,23 +135,22 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce const hasRemoteAuthority = !!this.remoteAuthority; let launchRemotely = hasRemoteAuthority || forceExtHostProcess; - this.userHome = this._environmentService.userHome; + const userHomeUri = await this._remotePathService.userHome; this.os = platform.OS; if (launchRemotely) { + this.userHome = userHomeUri.path; if (hasRemoteAuthority) { - this._remoteAgentService.getEnvironment().then(env => { - if (!env) { - return; - } - this.userHome = env.userHome.path; - this.os = env.os; - }); + const remoteEnv = await this._remoteAgentService.getEnvironment(); + if (remoteEnv) { + this.os = remoteEnv.os; + } } const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper); } else { - this._process = await this._launchProcess(shellLaunchConfig, cols, rows, isScreenReaderModeEnabled); + this.userHome = userHomeUri.fsPath; + this._process = await this._launchProcess(shellLaunchConfig, cols, rows, this.userHome, isScreenReaderModeEnabled); } } this.processState = ProcessState.LAUNCHING; @@ -191,6 +195,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, + userHome: string, isScreenReaderModeEnabled: boolean ): Promise { const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file); @@ -217,7 +222,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce const initialCwd = terminalEnvironment.getCwd( shellLaunchConfig, - this._environmentService.userHome, + userHome, lastActiveWorkspace, this._configurationResolverService, activeWorkspaceRootUri, @@ -230,6 +235,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 +314,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..667b3ed88da 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE } from 'vs/workbench/contrib/terminal/common/terminal'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; @@ -15,7 +14,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'; @@ -26,10 +25,13 @@ import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform'; import { basename } from 'vs/base/common/path'; -import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; +// TODO@daniel code layering +// eslint-disable-next-line code-layering, code-import-patterns +import { INativeOpenFileRequest } from 'vs/platform/windows/node/window'; import { find } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IDisposable } from 'vs/base/common/lifecycle'; interface IExtHostReadyEntry { promise: Promise; @@ -39,17 +41,19 @@ interface IExtHostReadyEntry { export class TerminalService implements ITerminalService { public _serviceBrand: undefined; - protected _isShuttingDown: boolean; - protected _terminalFocusContextKey: IContextKey; - protected _findWidgetVisible: IContextKey; - protected _terminalTabs: ITerminalTab[] = []; - protected _backgroundedTerminalInstances: ITerminalInstance[] = []; - protected get _terminalInstances(): ITerminalInstance[] { + private _isShuttingDown: boolean; + private _terminalFocusContextKey: IContextKey; + private _terminalShellTypeContextKey: IContextKey; + private _findWidgetVisible: IContextKey; + private _terminalTabs: ITerminalTab[] = []; + private _backgroundedTerminalInstances: ITerminalInstance[] = []; + private get _terminalInstances(): ITerminalInstance[] { return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), []); } 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; } @@ -60,38 +64,37 @@ export class TerminalService implements ITerminalService { public get configHelper(): ITerminalConfigHelper { return this._configHelper; } - protected readonly _onActiveTabChanged = new Emitter(); + private readonly _onActiveTabChanged = new Emitter(); public get onActiveTabChanged(): Event { return this._onActiveTabChanged.event; } - protected readonly _onInstanceCreated = new Emitter(); + private readonly _onInstanceCreated = new Emitter(); public get onInstanceCreated(): Event { return this._onInstanceCreated.event; } - protected readonly _onInstanceDisposed = new Emitter(); + private readonly _onInstanceDisposed = new Emitter(); public get onInstanceDisposed(): Event { return this._onInstanceDisposed.event; } - protected readonly _onInstanceProcessIdReady = new Emitter(); + private readonly _onInstanceProcessIdReady = new Emitter(); public get onInstanceProcessIdReady(): Event { return this._onInstanceProcessIdReady.event; } - protected readonly _onInstanceRequestSpawnExtHostProcess = new Emitter(); + private readonly _onInstanceRequestSpawnExtHostProcess = new Emitter(); public get onInstanceRequestSpawnExtHostProcess(): Event { return this._onInstanceRequestSpawnExtHostProcess.event; } - protected readonly _onInstanceRequestStartExtensionTerminal = new Emitter(); + private readonly _onInstanceRequestStartExtensionTerminal = new Emitter(); public get onInstanceRequestStartExtensionTerminal(): Event { return this._onInstanceRequestStartExtensionTerminal.event; } - protected readonly _onInstanceDimensionsChanged = new Emitter(); + private readonly _onInstanceDimensionsChanged = new Emitter(); public get onInstanceDimensionsChanged(): Event { return this._onInstanceDimensionsChanged.event; } - protected readonly _onInstanceMaximumDimensionsChanged = new Emitter(); + private readonly _onInstanceMaximumDimensionsChanged = new Emitter(); public get onInstanceMaximumDimensionsChanged(): Event { return this._onInstanceMaximumDimensionsChanged.event; } - protected readonly _onInstancesChanged = new Emitter(); + private readonly _onInstancesChanged = new Emitter(); public get onInstancesChanged(): Event { return this._onInstancesChanged.event; } - protected readonly _onInstanceTitleChanged = new Emitter(); + private readonly _onInstanceTitleChanged = new Emitter(); public get onInstanceTitleChanged(): Event { return this._onInstanceTitleChanged.event; } - protected readonly _onActiveInstanceChanged = new Emitter(); + private readonly _onActiveInstanceChanged = new Emitter(); public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } - protected readonly _onTabDisposed = new Emitter(); + private readonly _onTabDisposed = new Emitter(); public get onTabDisposed(): Event { return this._onTabDisposed.event; } - protected readonly _onRequestAvailableShells = new Emitter(); + private readonly _onRequestAvailableShells = new Emitter(); public get onRequestAvailableShells(): Event { return this._onRequestAvailableShells.event; } private readonly _terminalNativeService: ITerminalNativeService | undefined; constructor( @IContextKeyService private _contextKeyService: IContextKeyService, - @IPanelService private _panelService: IPanelService, @IWorkbenchLayoutService private _layoutService: IWorkbenchLayoutService, @ILifecycleService lifecycleService: ILifecycleService, @IDialogService private _dialogService: IDialogService, @@ -101,6 +104,7 @@ export class TerminalService implements ITerminalService { @IQuickInputService private _quickInputService: IQuickInputService, @IConfigurationService private _configurationService: IConfigurationService, @IViewsService private _viewsService: IViewsService, + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, @optional(ITerminalNativeService) terminalNativeService: ITerminalNativeService ) { // @optional could give undefined and properly typing it breaks service registration @@ -109,13 +113,14 @@ 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)); this._terminalNativeService.onOsResume(() => this._onOsResume()); } this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService); + this._terminalShellTypeContextKey = KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE.bindTo(this._contextKeyService); this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService); this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this._terminalNativeService?.linuxDistro || LinuxDistro.Unknown); this.onTabDisposed(tab => this._removeTab(tab)); @@ -176,19 +181,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,12 +196,21 @@ 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)); } - private async _onOpenFileRequest(request: IOpenFileRequest): Promise { + private async _onOpenFileRequest(request: INativeOpenFileRequest): Promise { // if the request to open files is coming in from the integrated terminal (identified though // the termProgram variable) and we are instructed to wait for editors close, wait for the // marker file to get deleted and then focus back to the integrated terminal. @@ -288,6 +297,13 @@ export class TerminalService implements ITerminalService { return tab.activeInstance; } + public doWithActiveInstance(callback: (terminal: ITerminalInstance) => T): T | void { + const instance = this.getActiveInstance(); + if (instance) { + return callback(instance); + } + } + public getInstanceFromId(terminalId: number): ITerminalInstance | undefined { let bgIndex = -1; this._backgroundedTerminalInstances.forEach((terminalInstance, i) => { @@ -411,6 +427,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 +480,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 @@ -532,8 +592,8 @@ export class TerminalService implements ITerminalService { }); } - public async selectDefaultWindowsShell(): Promise { - const shells = await this._detectWindowsShells(); + public async selectDefaultShell(): Promise { + const shells = await this._detectShells(); const options: IPickOptions = { placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") }; @@ -555,13 +615,13 @@ export class TerminalService implements ITerminalService { await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, shell, ConfigurationTarget.USER); } - private _detectWindowsShells(): Promise { + private _detectShells(): Promise { return new Promise(r => this._onRequestAvailableShells.fire({ callback: r })); } public createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance { - const instance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._configHelper, container, shellLaunchConfig); + const instance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._terminalShellTypeContextKey, this._configHelper, container, shellLaunchConfig); this._onInstanceCreated.fire(instance); return instance; } @@ -573,11 +633,7 @@ export class TerminalService implements ITerminalService { this._initInstanceListeners(instance); return instance; } - const terminalTab = this._instantiationService.createInstance(TerminalTab, - this._terminalFocusContextKey, - this.configHelper, - this._terminalContainer, - shell); + const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, shell); this._terminalTabs.push(terminalTab); const instance = terminalTab.terminalInstances[0]; terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); @@ -594,11 +650,7 @@ export class TerminalService implements ITerminalService { protected _showBackgroundTerminal(instance: ITerminalInstance): void { this._backgroundedTerminalInstances.splice(this._backgroundedTerminalInstances.indexOf(instance), 1); instance.shellLaunchConfig.hideFromUser = false; - const terminalTab = this._instantiationService.createInstance(TerminalTab, - this._terminalFocusContextKey, - this.configHelper, - this._terminalContainer, - instance); + const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, instance); this._terminalTabs.push(terminalTab); terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged)); @@ -648,9 +700,13 @@ export class TerminalService implements ITerminalService { } public hidePanel(): void { - const panel = this._panelService.getActivePanel(); - if (panel && panel.getId() === TERMINAL_VIEW_ID) { - this._layoutService.setPanelHidden(true); + // Hide the panel if the terminal is in the panel and it has no sibling views + const location = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID); + if (location === ViewContainerLocation.Panel) { + const panel = this._viewDescriptorService.getViewContainer(TERMINAL_VIEW_ID); + if (panel && this._viewDescriptorService.getViewDescriptors(panel).activeViewDescriptors.length === 1) { + this._layoutService.setPanelHidden(true); + } } } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index 68ff40e9135..6e36047e1be 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ 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 { IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IShellLaunchConfig, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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 +215,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; @@ -226,12 +227,11 @@ export class TerminalTab extends Disposable implements ITerminalTab { public readonly onInstancesChanged: Event = this._onInstancesChanged.event; constructor( - terminalFocusContextKey: IContextKey, - configHelper: ITerminalConfigHelper, private _container: HTMLElement | undefined, 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 +340,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 +400,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 a45eaa00771..204e685d1ad 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,8 @@ 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'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; const FIND_FOCUS_CLASS = 'find-focused'; @@ -41,6 +43,7 @@ export class TerminalViewPane extends ViewPane { private _parentDomElement: HTMLElement | undefined; private _terminalContainer: HTMLElement | undefined; private _findWidget: TerminalFindWidget | undefined; + private _splitTerminalAction: IAction | undefined; constructor( options: IViewPaneOptions, @@ -57,7 +60,7 @@ export class TerminalViewPane extends ViewPane { @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, ) { - super(options, keybindingService, _contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService); + super(options, keybindingService, _contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService); } protected renderBody(container: HTMLElement): void { @@ -79,7 +82,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(); @@ -114,19 +117,24 @@ export class TerminalViewPane extends ViewPane { })); // Force another layout (first is setContainers) since config has changed - this.layoutBody(this._terminalContainer.offsetWidth, this._terminalContainer.offsetHeight); + this.layoutBody(this._terminalContainer.offsetHeight, this._terminalContainer.offsetWidth); } protected layoutBody(height: number, width: number): void { this._terminalService.terminalTabs.forEach(t => t.layout(width, height)); + // Update orientation of split button icon + if (this._splitTerminalAction) { + this._splitTerminalAction.class = this.orientation === Orientation.HORIZONTAL ? SplitTerminalAction.HORIZONTAL_CLASS : SplitTerminalAction.VERTICAL_CLASS; + } } public getActions(): IAction[] { if (!this._actions) { + this._splitTerminalAction = this._instantiationService.createInstance(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL); this._actions = [ this._instantiationService.createInstance(SwitchTerminalAction, SwitchTerminalAction.ID, SwitchTerminalAction.LABEL), this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.SHORT_LABEL), - this._instantiationService.createInstance(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL), + this._splitTerminalAction, this._instantiationService.createInstance(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.PANEL_LABEL) ]; this._actions.forEach(a => { @@ -231,6 +239,13 @@ export class TerminalViewPane extends ViewPane { if (!terminal) { return; } + + // copyPaste: Shift+right click should open context menu + if (rightClickBehavior === 'copyPaste' && event.shiftKey) { + this._openContextMenu(event); + return; + } + if (rightClickBehavior === 'copyPaste' && terminal.hasSelection()) { await terminal.copySelection(); terminal.clearSelection(); @@ -252,13 +267,7 @@ export class TerminalViewPane extends ViewPane { })); this._register(dom.addDisposableListener(parentDomElement, 'contextmenu', (event: MouseEvent) => { if (!this._cancelContextMenu) { - const standardEvent = new StandardMouseEvent(event); - const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy }; - this._contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => this._getContextMenuActions(), - getActionsContext: () => this._parentDomElement - }); + this._openContextMenu(event); } event.preventDefault(); event.stopImmediatePropagation(); @@ -305,9 +314,19 @@ export class TerminalViewPane extends ViewPane { })); } - private _updateTheme(theme?: ITheme): void { + private _openContextMenu(event: MouseEvent): void { + const standardEvent = new StandardMouseEvent(event); + const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy }; + this._contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => this._getContextMenuActions(), + getActionsContext: () => this._parentDomElement + }); + } + + private _updateTheme(theme?: IColorTheme): void { if (!theme) { - theme = this.themeService.getTheme(); + theme = this.themeService.getColorTheme(); } if (this._findWidget) { @@ -321,13 +340,16 @@ export class TerminalViewPane extends ViewPane { } // TODO: Can we support ligatures? // dom.toggleClass(this._parentDomElement, 'enable-ligatures', this._terminalService.configHelper.config.fontLigatures); - this.layoutBody(this._parentDomElement.offsetWidth, this._parentDomElement.offsetHeight); + this.layoutBody(this._parentDomElement.offsetHeight, this._parentDomElement.offsetWidth); } } -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 e58b63cc000..dc2d844b8ae 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { OperatingSystem } from 'vs/base/common/platform'; @@ -16,28 +16,35 @@ export const TERMINAL_VIEW_ID = 'workbench.panel.terminal'; /** A context key that is set when there is at least one opened integrated terminal. */ export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey('terminalIsOpen', false); + /** A context key that is set when the integrated terminal has focus. */ export const KEYBINDING_CONTEXT_TERMINAL_FOCUS = new RawContextKey('terminalFocus', false); + +export const KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY = 'terminalShellType'; +/** A context key that is set to the detected shell for the most recently active terminal, this is set to the last known value when no terminals exist. */ +export const KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE = new RawContextKey(KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, undefined); + /** A context key that is set when the integrated terminal does not have focus. */ -export const KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FOCUS.toNegated(); +export const KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED = KEYBINDING_CONTEXT_TERMINAL_FOCUS.toNegated(); + /** A context key that is set when the user is navigating the accessibility tree */ export const KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS = new RawContextKey('terminalA11yTreeFocus', false); /** A keybinding context key that is set when the integrated terminal has text selected. */ export const KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED = new RawContextKey('terminalTextSelected', false); /** A keybinding context key that is set when the integrated terminal does not have text selected. */ -export const KEYBINDING_CONTEXT_TERMINAL_TEXT_NOT_SELECTED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.toNegated(); +export const KEYBINDING_CONTEXT_TERMINAL_TEXT_NOT_SELECTED = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.toNegated(); /** A context key that is set when the find widget in integrated terminal is visible. */ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE = new RawContextKey('terminalFindWidgetVisible', false); /** A context key that is set when the find widget in integrated terminal is not visible. */ -export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.toNegated(); +export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.toNegated(); /** A context key that is set when the find widget find input in integrated terminal is focused. */ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED = new RawContextKey('terminalFindWidgetInputFocused', false); /** A context key that is set when the find widget in integrated terminal is focused. */ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED = new RawContextKey('terminalFindWidgetFocused', false); /** A context key that is set when the find widget find input in integrated terminal is not focused. */ -export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.toNegated(); +export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_NOT_FOCUSED = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.toNegated(); export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed'; export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime'; @@ -107,6 +114,7 @@ export interface ITerminalConfiguration { scrollback: number; commandsToSkipShell: string[]; allowChords: boolean; + allowMnemonics: boolean; cwd: string; confirmOnExit: boolean; enableBell: boolean; @@ -119,6 +127,7 @@ export interface ITerminalConfiguration { showExitAlert: boolean; splitCwd: 'workspaceRoot' | 'initial' | 'inherited'; windowsEnableConpty: boolean; + wordSeparators: string; experimentalUseTitleEvent: boolean; enableFileLinks: boolean; unicodeVersion: '6' | '11'; @@ -475,3 +484,122 @@ export const enum TERMINAL_COMMAND_ID { NAVIGATION_MODE_FOCUS_NEXT = 'workbench.action.terminal.navigationModeFocusNext', NAVIGATION_MODE_FOCUS_PREVIOUS = 'workbench.action.terminal.navigationModeFocusPrevious' } + +export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ + TERMINAL_COMMAND_ID.CLEAR_SELECTION, + TERMINAL_COMMAND_ID.CLEAR, + TERMINAL_COMMAND_ID.COPY_SELECTION, + TERMINAL_COMMAND_ID.DELETE_TO_LINE_START, + TERMINAL_COMMAND_ID.DELETE_WORD_LEFT, + TERMINAL_COMMAND_ID.DELETE_WORD_RIGHT, + TERMINAL_COMMAND_ID.FIND_WIDGET_FOCUS, + TERMINAL_COMMAND_ID.FIND_WIDGET_HIDE, + TERMINAL_COMMAND_ID.FIND_NEXT, + TERMINAL_COMMAND_ID.FIND_PREVIOUS, + TERMINAL_COMMAND_ID.TOGGLE_FIND_REGEX, + TERMINAL_COMMAND_ID.TOGGLE_FIND_WHOLE_WORD, + TERMINAL_COMMAND_ID.TOGGLE_FIND_CASE_SENSITIVE, + TERMINAL_COMMAND_ID.FOCUS_NEXT_PANE, + TERMINAL_COMMAND_ID.FOCUS_NEXT, + TERMINAL_COMMAND_ID.FOCUS_PREVIOUS_PANE, + TERMINAL_COMMAND_ID.FOCUS_PREVIOUS, + TERMINAL_COMMAND_ID.FOCUS, + TERMINAL_COMMAND_ID.KILL, + TERMINAL_COMMAND_ID.MOVE_TO_LINE_END, + TERMINAL_COMMAND_ID.MOVE_TO_LINE_START, + TERMINAL_COMMAND_ID.NEW_IN_ACTIVE_WORKSPACE, + TERMINAL_COMMAND_ID.NEW, + TERMINAL_COMMAND_ID.PASTE, + TERMINAL_COMMAND_ID.RESIZE_PANE_DOWN, + TERMINAL_COMMAND_ID.RESIZE_PANE_LEFT, + TERMINAL_COMMAND_ID.RESIZE_PANE_RIGHT, + TERMINAL_COMMAND_ID.RESIZE_PANE_UP, + TERMINAL_COMMAND_ID.RUN_ACTIVE_FILE, + TERMINAL_COMMAND_ID.RUN_SELECTED_TEXT, + TERMINAL_COMMAND_ID.SCROLL_DOWN_LINE, + TERMINAL_COMMAND_ID.SCROLL_DOWN_PAGE, + TERMINAL_COMMAND_ID.SCROLL_TO_BOTTOM, + TERMINAL_COMMAND_ID.SCROLL_TO_NEXT_COMMAND, + TERMINAL_COMMAND_ID.SCROLL_TO_PREVIOUS_COMMAND, + TERMINAL_COMMAND_ID.SCROLL_TO_TOP, + TERMINAL_COMMAND_ID.SCROLL_UP_LINE, + TERMINAL_COMMAND_ID.SCROLL_UP_PAGE, + TERMINAL_COMMAND_ID.SEND_SEQUENCE, + TERMINAL_COMMAND_ID.SELECT_ALL, + TERMINAL_COMMAND_ID.SELECT_TO_NEXT_COMMAND, + TERMINAL_COMMAND_ID.SELECT_TO_NEXT_LINE, + TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_COMMAND, + TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_LINE, + TERMINAL_COMMAND_ID.SPLIT_IN_ACTIVE_WORKSPACE, + TERMINAL_COMMAND_ID.SPLIT, + TERMINAL_COMMAND_ID.TOGGLE, + TERMINAL_COMMAND_ID.NAVIGATION_MODE_EXIT, + TERMINAL_COMMAND_ID.NAVIGATION_MODE_FOCUS_NEXT, + TERMINAL_COMMAND_ID.NAVIGATION_MODE_FOCUS_PREVIOUS, + 'editor.action.toggleTabFocusMode', + 'workbench.action.quickOpen', + 'workbench.action.quickOpenPreviousEditor', + 'workbench.action.showCommands', + 'workbench.action.tasks.build', + 'workbench.action.tasks.restartTask', + 'workbench.action.tasks.runTask', + 'workbench.action.tasks.reRunTask', + 'workbench.action.tasks.showLog', + 'workbench.action.tasks.showTasks', + 'workbench.action.tasks.terminate', + 'workbench.action.tasks.test', + 'workbench.action.toggleFullScreen', + 'workbench.action.terminal.focusAtIndex1', + 'workbench.action.terminal.focusAtIndex2', + 'workbench.action.terminal.focusAtIndex3', + 'workbench.action.terminal.focusAtIndex4', + 'workbench.action.terminal.focusAtIndex5', + 'workbench.action.terminal.focusAtIndex6', + 'workbench.action.terminal.focusAtIndex7', + 'workbench.action.terminal.focusAtIndex8', + 'workbench.action.terminal.focusAtIndex9', + 'workbench.action.focusSecondEditorGroup', + 'workbench.action.focusThirdEditorGroup', + 'workbench.action.focusFourthEditorGroup', + 'workbench.action.focusFifthEditorGroup', + 'workbench.action.focusSixthEditorGroup', + 'workbench.action.focusSeventhEditorGroup', + 'workbench.action.focusEighthEditorGroup', + 'workbench.action.nextPanelView', + 'workbench.action.previousPanelView', + 'workbench.action.nextSideBarView', + 'workbench.action.previousSideBarView', + 'workbench.action.debug.start', + 'workbench.action.debug.stop', + 'workbench.action.debug.run', + 'workbench.action.debug.restart', + 'workbench.action.debug.continue', + 'workbench.action.debug.pause', + 'workbench.action.debug.stepInto', + 'workbench.action.debug.stepOut', + 'workbench.action.debug.stepOver', + 'workbench.action.nextEditor', + 'workbench.action.previousEditor', + 'workbench.action.nextEditorInGroup', + 'workbench.action.previousEditorInGroup', + 'workbench.action.openNextRecentlyUsedEditor', + 'workbench.action.openPreviousRecentlyUsedEditor', + 'workbench.action.openNextRecentlyUsedEditorInGroup', + 'workbench.action.openPreviousRecentlyUsedEditorInGroup', + 'workbench.action.quickOpenPreviousRecentlyUsedEditor', + 'workbench.action.quickOpenLeastRecentlyUsedEditor', + 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup', + 'workbench.action.quickOpenLeastRecentlyUsedEditorInGroup', + 'workbench.action.focusActiveEditorGroup', + 'workbench.action.focusFirstEditorGroup', + 'workbench.action.focusLastEditorGroup', + 'workbench.action.firstEditorInGroup', + 'workbench.action.lastEditorInGroup', + 'workbench.action.navigateUp', + 'workbench.action.navigateDown', + 'workbench.action.navigateRight', + 'workbench.action.navigateLeft', + 'workbench.action.togglePanel', + 'workbench.action.quickOpenView', + 'workbench.action.toggleMaximizedPanel' +]; 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/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts new file mode 100644 index 00000000000..ee9846eb41c --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -0,0 +1,340 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; +import { localize } from 'vs/nls'; +import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; +import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL } from 'vs/workbench/contrib/terminal/common/terminal'; +import { isMacintosh, isWindows, Platform } from 'vs/base/common/platform'; + +export const terminalConfiguration: IConfigurationNode = { + id: 'terminal', + order: 100, + title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), + type: 'object', + properties: { + 'terminal.integrated.automationShell.linux': { + markdownDescription: localize('terminal.integrated.automationShell.linux', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`'), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.automationShell.osx': { + markdownDescription: localize('terminal.integrated.automationShell.osx', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`'), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.automationShell.windows': { + markdownDescription: localize('terminal.integrated.automationShell.windows', "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`'), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.shellArgs.linux': { + markdownDescription: localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: 'array', + items: { + type: 'string' + }, + default: [] + }, + 'terminal.integrated.shellArgs.osx': { + markdownDescription: localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: 'array', + items: { + type: 'string' + }, + // Unlike on Linux, ~/.profile is not sourced when logging into a macOS session. This + // is the reason terminals on macOS typically run login shells by default which set up + // the environment. See http://unix.stackexchange.com/a/119675/115410 + default: ['-l'] + }, + 'terminal.integrated.shellArgs.windows': { + markdownDescription: localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + 'anyOf': [ + { + type: 'array', + items: { + type: 'string', + markdownDescription: localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).") + }, + }, + { + type: 'string', + markdownDescription: localize('terminal.integrated.shellArgs.windows.string', "The command line arguments in [command-line format](https://msdn.microsoft.com/en-au/08dfcab2-eb6e-49a4-80eb-87d4076c98c6) to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).") + } + ], + default: [] + }, + 'terminal.integrated.macOptionIsMeta': { + description: localize('terminal.integrated.macOptionIsMeta', "Controls whether to treat the option key as the meta key in the terminal on macOS."), + type: 'boolean', + default: false + }, + 'terminal.integrated.macOptionClickForcesSelection': { + description: localize('terminal.integrated.macOptionClickForcesSelection', "Controls whether to force selection when using Option+click on macOS. This will force a regular (line) selection and disallow the use of column selection mode. This enables copying and pasting using the regular terminal selection, for example, when mouse mode is enabled in tmux."), + type: 'boolean', + default: false + }, + 'terminal.integrated.copyOnSelection': { + description: localize('terminal.integrated.copyOnSelection', "Controls whether text selected in the terminal will be copied to the clipboard."), + type: 'boolean', + default: false + }, + 'terminal.integrated.drawBoldTextInBrightColors': { + description: localize('terminal.integrated.drawBoldTextInBrightColors', "Controls whether bold text in the terminal will always use the \"bright\" ANSI color variant."), + type: 'boolean', + default: true + }, + 'terminal.integrated.fontFamily': { + markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."), + type: 'string' + }, + // TODO: Support font ligatures + // 'terminal.integrated.fontLigatures': { + // 'description': localize('terminal.integrated.fontLigatures', "Controls whether font ligatures are enabled in the terminal."), + // 'type': 'boolean', + // 'default': false + // }, + 'terminal.integrated.fontSize': { + description: localize('terminal.integrated.fontSize', "Controls the font size in pixels of the terminal."), + type: 'number', + default: EDITOR_FONT_DEFAULTS.fontSize + }, + 'terminal.integrated.letterSpacing': { + description: localize('terminal.integrated.letterSpacing', "Controls the letter spacing of the terminal, this is an integer value which represents the amount of additional pixels to add between characters."), + type: 'number', + default: DEFAULT_LETTER_SPACING + }, + 'terminal.integrated.lineHeight': { + description: localize('terminal.integrated.lineHeight', "Controls the line height of the terminal, this number is multiplied by the terminal font size to get the actual line-height in pixels."), + type: 'number', + default: DEFAULT_LINE_HEIGHT + }, + 'terminal.integrated.minimumContrastRatio': { + markdownDescription: localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: [WCAG AA compliance (minimum)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html).\n- 7: [WCAG AAA compliance (enhanced)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast7.html).\n- 21: White on black or black on white."), + type: 'number', + default: 1 + }, + 'terminal.integrated.fastScrollSensitivity': { + markdownDescription: localize('terminal.integrated.fastScrollSensitivity', "Scrolling speed multiplier when pressing `Alt`."), + type: 'number', + default: 5 + }, + 'terminal.integrated.mouseWheelScrollSensitivity': { + markdownDescription: localize('terminal.integrated.mouseWheelScrollSensitivity', "A multiplier to be used on the `deltaY` of mouse wheel scroll events."), + type: 'number', + default: 1 + }, + 'terminal.integrated.fontWeight': { + type: 'string', + enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], + description: localize('terminal.integrated.fontWeight', "The font weight to use within the terminal for non-bold text."), + default: 'normal' + }, + 'terminal.integrated.fontWeightBold': { + type: 'string', + enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], + description: localize('terminal.integrated.fontWeightBold', "The font weight to use within the terminal for bold text."), + default: 'bold' + }, + 'terminal.integrated.cursorBlinking': { + description: localize('terminal.integrated.cursorBlinking', "Controls whether the terminal cursor blinks."), + type: 'boolean', + default: false + }, + 'terminal.integrated.cursorStyle': { + description: localize('terminal.integrated.cursorStyle', "Controls the style of terminal cursor."), + enum: [TerminalCursorStyle.BLOCK, TerminalCursorStyle.LINE, TerminalCursorStyle.UNDERLINE], + default: TerminalCursorStyle.BLOCK + }, + 'terminal.integrated.cursorWidth': { + markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when `#terminal.integrated.cursorStyle#` is set to `line`."), + type: 'number', + default: 1 + }, + 'terminal.integrated.scrollback': { + description: localize('terminal.integrated.scrollback', "Controls the maximum amount of lines the terminal keeps in its buffer."), + type: 'number', + default: 1000 + }, + 'terminal.integrated.detectLocale': { + markdownDescription: localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since VS Code's terminal only supports UTF-8 encoded data coming from the shell."), + type: 'string', + enum: ['auto', 'off', 'on'], + markdownEnumDescriptions: [ + localize('terminal.integrated.detectLocale.auto', "Set the `$LANG` environment variable if the existing variable does not exist or it does not end in `'.UTF-8'`."), + localize('terminal.integrated.detectLocale.off', "Do not set the `$LANG` environment variable."), + localize('terminal.integrated.detectLocale.on', "Always set the `$LANG` environment variable.") + ], + default: 'auto' + }, + 'terminal.integrated.rendererType': { + type: 'string', + enum: ['auto', 'canvas', 'dom', 'experimentalWebgl'], + markdownEnumDescriptions: [ + localize('terminal.integrated.rendererType.auto', "Let VS Code guess which renderer to use."), + localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer."), + localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer."), + localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) and this will only be enabled for new terminals (not hot swappable like the other renderers).") + ], + default: 'auto', + description: localize('terminal.integrated.rendererType', "Controls how the terminal is rendered.") + }, + 'terminal.integrated.rightClickBehavior': { + type: 'string', + enum: ['default', 'copyPaste', 'paste', 'selectWord'], + enumDescriptions: [ + localize('terminal.integrated.rightClickBehavior.default', "Show the context menu."), + localize('terminal.integrated.rightClickBehavior.copyPaste', "Copy when there is a selection, otherwise paste."), + localize('terminal.integrated.rightClickBehavior.paste', "Paste on right click."), + localize('terminal.integrated.rightClickBehavior.selectWord', "Select the word under the cursor and show the context menu.") + ], + default: isMacintosh ? 'selectWord' : isWindows ? 'copyPaste' : 'default', + description: localize('terminal.integrated.rightClickBehavior', "Controls how terminal reacts to right click.") + }, + 'terminal.integrated.cwd': { + description: localize('terminal.integrated.cwd', "An explicit start path where the terminal will be launched, this is used as the current working directory (cwd) for the shell process. This may be particularly useful in workspace settings if the root directory is not a convenient cwd."), + type: 'string', + default: undefined + }, + 'terminal.integrated.confirmOnExit': { + description: localize('terminal.integrated.confirmOnExit', "Controls whether to confirm on exit if there are active terminal sessions."), + type: 'boolean', + default: false + }, + 'terminal.integrated.enableBell': { + description: localize('terminal.integrated.enableBell', "Controls whether the terminal bell is enabled."), + type: 'boolean', + default: false + }, + 'terminal.integrated.commandsToSkipShell': { + markdownDescription: localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')), + type: 'array', + items: { + type: 'string' + }, + default: [] + }, + 'terminal.integrated.allowChords': { + markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `#terminal.integrated.commandsToSkipShell#`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), + type: 'boolean', + default: true + }, + 'terminal.integrated.allowMnemonics': { + markdownDescription: 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: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code. This is not supported on Windows."), + type: 'boolean', + default: true + }, + 'terminal.integrated.env.osx': { + markdownDescription: localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the VS Code process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), + type: 'object', + additionalProperties: { + type: ['string', 'null'] + }, + default: {} + }, + 'terminal.integrated.env.linux': { + markdownDescription: localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), + type: 'object', + additionalProperties: { + type: ['string', 'null'] + }, + default: {} + }, + 'terminal.integrated.env.windows': { + markdownDescription: localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), + type: 'object', + additionalProperties: { + type: ['string', 'null'] + }, + default: {} + }, + 'terminal.integrated.showExitAlert': { + description: localize('terminal.integrated.showExitAlert', "Controls whether to show the alert \"The terminal process terminated with exit code\" when exit code is non-zero."), + type: 'boolean', + default: true + }, + 'terminal.integrated.splitCwd': { + description: localize('terminal.integrated.splitCwd', "Controls the working directory a split terminal starts with."), + type: 'string', + enum: ['workspaceRoot', 'initial', 'inherited'], + enumDescriptions: [ + localize('terminal.integrated.splitCwd.workspaceRoot', "A new split terminal will use the workspace root as the working directory. In a multi-root workspace a choice for which root folder to use is offered."), + localize('terminal.integrated.splitCwd.initial', "A new split terminal will use the working directory that the parent terminal started with."), + localize('terminal.integrated.splitCwd.inherited', "On macOS and Linux, a new split terminal will use the working directory of the parent terminal. On Windows, this behaves the same as initial."), + ], + default: 'inherited' + }, + 'terminal.integrated.windowsEnableConpty': { + description: localize('terminal.integrated.windowsEnableConpty', "Whether to use ConPTY for Windows terminal process communication (requires Windows 10 build number 18309+). Winpty will be used if this is false."), + type: 'boolean', + default: true + }, + 'terminal.integrated.wordSeparators': { + description: localize('terminal.integrated.wordSeparators', "A string containing all characters to be considered word separators by the double click to select word feature."), + type: 'string', + default: ' ()[]{}\',"`' + }, + 'terminal.integrated.experimentalUseTitleEvent': { + description: localize('terminal.integrated.experimentalUseTitleEvent', "An experimental setting that will use the terminal title event for the dropdown title. This setting will only apply to new terminals."), + type: 'boolean', + default: false + }, + 'terminal.integrated.enableFileLinks': { + description: localize('terminal.integrated.enableFileLinks', "Whether to enable file links in the terminal. Links can be slow when working on a network drive in particular because each file link is verified against the file system."), + type: 'boolean', + default: true + }, + 'terminal.integrated.unicodeVersion': { + type: 'string', + enum: ['6', '11'], + enumDescriptions: [ + localize('terminal.integrated.unicodeVersion.six', "Version 6 of unicode, this is an older version which should work better on older systems."), + localize('terminal.integrated.unicodeVersion.eleven', "Version 11 of unicode, this version provides better support on modern systems that use modern versions of unicode.") + ], + default: '11', + description: localize('terminal.integrated.unicodeVersion', "Controls what version of unicode to use when evaluating the width of characters in the terminal. If you experience emoji or other wide characters not taking up the right amount of space or backspace either deleting too much or too little then you may want to try tweaking this setting.") + } + } +}; + +export function getTerminalShellConfiguration(getSystemShell?: (p: Platform) => string): IConfigurationNode { + return { + id: 'terminal', + order: 100, + title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), + type: 'object', + properties: { + 'terminal.integrated.shell.linux': { + markdownDescription: + getSystemShell + ? localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Linux)) + : localize('terminal.integrated.shell.linux.noDefault', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.shell.osx': { + markdownDescription: + getSystemShell + ? localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Mac)) + : localize('terminal.integrated.shell.osx.noDefault', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.shell.windows': { + markdownDescription: + getSystemShell + ? localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Windows)) + : localize('terminal.integrated.shell.windows.noDefault', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: ['string', 'null'], + default: null + } + } + }; +} diff --git a/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts b/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts deleted file mode 100644 index c08a6f47fe8..00000000000 --- a/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts +++ /dev/null @@ -1,45 +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 { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Platform } from 'vs/base/common/platform'; - -export function registerShellConfiguration(getSystemShell?: (p: Platform) => string): void { - const configurationRegistry = Registry.as(Extensions.Configuration); - configurationRegistry.registerConfiguration({ - id: 'terminal', - order: 100, - title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), - type: 'object', - properties: { - 'terminal.integrated.shell.linux': { - markdownDescription: - getSystemShell - ? nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Linux)) - : nls.localize('terminal.integrated.shell.linux.noDefault', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: ['string', 'null'], - default: null - }, - 'terminal.integrated.shell.osx': { - markdownDescription: - getSystemShell - ? nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Mac)) - : nls.localize('terminal.integrated.shell.osx.noDefault', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: ['string', 'null'], - default: null - }, - 'terminal.integrated.shell.windows': { - markdownDescription: - getSystemShell - ? nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Windows)) - : nls.localize('terminal.integrated.shell.windows.noDefault', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: ['string', 'null'], - default: null - } - } - }); -} diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts index f9dd46267ca..1721bf226a6 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts @@ -7,10 +7,18 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/electron-browser/terminalInstanceService'; import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal'; -import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig'; import { TerminalNativeService } from 'vs/workbench/contrib/terminal/electron-browser/terminalNativeService'; import { ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration'; -registerShellConfiguration(getSystemShell); +// This file contains additional desktop-only contributions on top of those in browser/ + +// Register services registerSingleton(ITerminalNativeService, TerminalNativeService, true); registerSingleton(ITerminalInstanceService, TerminalInstanceService, true); + +// Register configurations +const configurationRegistry = Registry.as(Extensions.Configuration); +configurationRegistry.registerConfiguration(getTerminalShellConfiguration(getSystemShell)); diff --git a/src/vs/workbench/contrib/terminal/node/terminal.ts b/src/vs/workbench/contrib/terminal/node/terminal.ts index 2083444e7cf..c672f252827 100644 --- a/src/vs/workbench/contrib/terminal/node/terminal.ts +++ b/src/vs/workbench/contrib/terminal/node/terminal.ts @@ -109,8 +109,8 @@ async function detectAvailableWindowsShells(): Promise { const expectedLocations: { [key: string]: string[] } = { 'Command Prompt': [`${system32Path}\\cmd.exe`], - PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], - 'PowerShell Core': [await getShellPathFromRegistry('pwsh')], + 'Windows PowerShell': [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], + 'PowerShell': [await getShellPathFromRegistry('pwsh')], 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], 'Git Bash': [ `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, @@ -125,7 +125,7 @@ async function detectAvailableWindowsShells(): Promise { // `${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe` // ] }; - const promises: PromiseLike[] = []; + const promises: Promise[] = []; Object.keys(expectedLocations).forEach(key => promises.push(validateShellPaths(key, expectedLocations[key]))); const shells = await Promise.all(promises); return coalesce(shells); 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 ae431e80709..7d965ad50da 100644 --- a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css +++ b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css @@ -7,9 +7,71 @@ position: relative; } +.monaco-workbench .timeline-view.pane-header .description { + display: block; + font-weight: normal; + margin-left: 10px; + opacity: 0.6; + overflow: hidden; + text-overflow: ellipsis; + text-transform: none; + white-space: nowrap; +} + +.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; +} + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label { + flex: 1; + text-overflow: ellipsis; + overflow: hidden; +} + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container { + margin-left: 2px; + margin-right: 4px; + 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--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 142fb5cb596..332435d6edd 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -9,18 +9,21 @@ 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 product from 'vs/platform/product/common/product'; +import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; +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 containerIcon = 'codicon-history'; readonly ctorDescriptor = new SyncDescriptor(TimelinePane); - readonly when = ContextKeyExpr.equals('config.timeline.showView', true); readonly order = 2; readonly weight = 30; readonly collapsed = true; @@ -39,16 +42,40 @@ 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: false //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"), + default: null }, } }); -if (product.quality !== 'stable') { - Registry.as(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER); +Registry.as(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER); + +namespace OpenTimelineAction { + + 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.setUri(arg); + }; + } } +CommandsRegistry.registerCommand(OpenTimelineAction.ID, OpenTimelineAction.handler()); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({ + group: '4_timeline', + order: 1, + command: { + id: OpenTimelineAction.ID, + title: OpenTimelineAction.LABEL, + icon: { id: 'codicon/history' } + }, + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) +})); + 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 241ee3dabc0..38203d471c5 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -8,49 +8,226 @@ 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 { DisposableStore } from 'vs/base/common/lifecycle'; +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 } 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 { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TimelineItem, ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline'; +import { ContextKeyExpr, IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +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'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { debounce } from 'vs/base/common/decorators'; 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, 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'; -type TreeElement = TimelineItem; +const PageSize = 20; -// TODO[ECA]: Localize all the strings +type TreeElement = TimelineItem | LoadMoreCommand; + +function isLoadMoreCommand(item: TreeElement | undefined): item is LoadMoreCommand { + return item instanceof LoadMoreCommand; +} + +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; +} + +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; + } +} + +class LoadMoreCommand { + readonly handle = 'vscode-command:loadMore'; + readonly timestamp = 0; + readonly description = undefined; + readonly detail = undefined; + readonly contextValue = undefined; + // Make things easier for duck typing + readonly id = undefined; + readonly icon = undefined; + readonly iconDark = undefined; + readonly source = undefined; + readonly relativeTime = undefined; + readonly hideRelativeTime = undefined; + + constructor(loading: boolean) { + this._loading = loading; + } + private _loading: boolean = false; + get loading(): boolean { + return this._loading; + } + set loading(value: boolean) { + this._loading = value; + } + + get ariaLabel() { + return this.label; + } + + get label() { + return this.loading ? localize('timeline.loadingMore', "Loading...") : localize('timeline.loadMore', "Load more"); + } + + get themeIcon(): { id: string; } | undefined { + return undefined; //this.loading ? { id: 'sync~spin' } : undefined; + } +} + +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 _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 | undefined; - private _items: TimelineItemWithSource[] = []; - 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, @@ -66,14 +243,79 @@ export class TimelinePane extends ViewPane { @ITimelineService protected timelineService: ITimelineService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('view', TimelinePane.ID); + this.commands = this._register(this.instantiationService.createInstance(TimelinePaneCommands, this)); + + this.followActiveEditorContext = TimelineFollowActiveEditorContext.bindTo(this.contextKeyService); + + 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) { + if (!e.affectsConfiguration('timeline.excludeSources')) { + return; + } + + 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; @@ -81,34 +323,73 @@ 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.loadTimeline(); + 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) { - this.loadTimeline(e.added); + this.loadTimeline(true, e.added); } } private onTimelineChanged(e: TimelineChangeEvent) { - if (e.uri === undefined || e.uri.toString(true) !== this._uri?.toString(true)) { - this.loadTimeline([e.id]); + 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); + } else { + timeline.invalidate(e.reset); + } } } + 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; get message(): string | undefined { return this._message; @@ -120,7 +401,7 @@ export class TimelinePane extends ViewPane { } private updateMessage(): void { - if (this._message) { + if (this._message !== undefined) { this.showMessage(this._message); } else { this.hideMessage(); @@ -128,125 +409,362 @@ 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(sources?: string[]) { - // If we have no source, we are reseting all sources, so cancel everything in flight and reset caches - if (sources === undefined) { - this._items.length = 0; + private _isEmpty = true; + private _maxItemCount = 0; - if (this._loadingMessageTimer) { - clearTimeout(this._loadingMessageTimer); - this._loadingMessageTimer = undefined; - } + private _visibleItemCount = 0; + private get hasVisibleItems() { + return this._visibleItemCount > 0; + } - for (const { tokenSource } of this._pendingRequests.values()) { + 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(); + this.pendingRequests.clear(); - // 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 = 'The active editor cannot provide timeline information.'; - this._tree.setChildren(null, undefined); + 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.clear(true); + } + + // 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 (this._uri !== undefined) { - this._loadingMessageTimer = setTimeout((uri: URI) => { - if (uri !== this._uri) { - return; - } - - this._tree.setChildren(null, undefined); - this.message = `Loading timeline for ${basename(uri.fsPath)}...`; - }, 500, this._uri); + if (this._isEmpty && this.uri !== undefined) { + this.setLoadingUriMessage(); } } - if (this._uri === undefined) { + if (this.uri === undefined) { + this.clear(false); + this.refresh(); + return; } - for (const source of sources ?? this.timelineService.getSources()) { - let request = this._pendingRequests.get(source); - request?.tokenSource.dispose(true); + if (!this.isBodyVisible()) { + return; + } - request = this.timelineService.getTimelineRequest(source, this._uri, new CancellationTokenSource())!; + let hasPendingRequests = false; - this._pendingRequests.set(source, request); - request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); + for (const source of sources ?? this.timelineService.getSources().map(s => s.id)) { + const requested = this.loadTimelineForSource(source, this.uri, reset); + if (requested) { + hasPendingRequests = true; + } + } - 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 && + options?.cursor !== undefined && + 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 items; + let response: Timeline | undefined; try { - items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.items); + response = await this.progressService.withProgress({ location: this.id }, () => request.result); + } + finally { + this.pendingRequests.delete(request.source); } - catch { } - this._pendingRequests.delete(request.source); - if (request.tokenSource.token.isCancellationRequested || request.uri !== this._uri) { + if ( + response === undefined || + request.tokenSource.token.isCancellationRequested || + request.uri !== this.uri + ) { + if (this.pendingRequests.size === 0 && this._pendingRefresh) { + this.refresh(); + } + return; } - this.replaceItems(request.source, items); + const source = request.source; + + 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) { + if (this._pendingRefresh) { + this.refresh(); + } else { + this.tree.rerender(); + } + } } - private replaceItems(source: string, items?: TimelineItemWithSource[]) { - const hasItems = this._items.length !== 0; + private *getItems(): Generator, any, any> { + let more = false; + + if (this.uri === undefined || this.timelinesBySource.size === 0) { + this._visibleItemCount = 0; - if (items?.length) { - this._items.splice(0, this._items.length, ...this._items.filter(i => i.source !== source), ...items); - this._items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })); - } - else if (this._items.length && this._items.some(i => i.source === source)) { - this._items = this._items.filter(i => i.source !== source); - } - else { return; } - // If we have items already and there are other pending requests, debounce for a bit to wait for other requests - if (hasItems && this._pendingRequests.size !== 0) { - this.refreshDebounced(); + const maxCount = this._maxItemCount; + let count = 0; + + if (this.timelinesBySource.size === 1) { + const [source, timeline] = Iterable.first(this.timelinesBySource); + + timeline.lastRenderedIndex = -1; + + if (this.excludedSources.has(source)) { + this._visibleItemCount = 0; + + return; + } + + 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; + } + + 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 { - this.refresh(); + 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._visibleItemCount = count; + + if (more) { + yield { + element: new LoadMoreCommand(this.pendingRequests.size !== 0) + }; + } else if (this.pendingRequests.size !== 0) { + yield { + element: new LoadMoreCommand(true) + }; } } private refresh() { - if (this._loadingMessageTimer) { - clearTimeout(this._loadingMessageTimer); - this._loadingMessageTimer = undefined; + if (!this.isBodyVisible()) { + return; } - if (this._items.length === 0) { - this.message = '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.map(item => ({ element: item }))); + this._pendingRefresh = false; } @debounce(500) @@ -256,96 +774,260 @@ 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.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 = 'The active editor cannot provide timeline information.'; + 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); - const renderer = this.instantiationService.createInstance(TimelineTreeRenderer); - this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', this._treeElement, new TimelineListVirtualDelegate(), [renderer], { + this.treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this.commands); + this.tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', + this.$tree, new TimelineListVirtualDelegate(), [this.treeRenderer], { identityProvider: new TimelineIdentityProvider(), + accessibilityProvider: { + getAriaLabel(element: TreeElement): string { + if (isLoadMoreCommand(element)) { + return element.ariaLabel; + } + return element.ariaLabel ?? localize('timeline.aria.item', "{0}: {1}", element.relativeTime ?? '', element.label); + } + }, + ariaLabel: this.title, keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider(), overrideStyles: { - listBackground: this.getBackgroundColor() + listBackground: this.getBackgroundColor(), + } }); - const customTreeNavigator = new TreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); + const customTreeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); this._register(customTreeNavigator); + 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 command = selection.length === 1 ? selection[0]?.command : undefined; - if (command) { - this.commandService.executeCommand(command.id, ...(command.arguments || [])); + const selection = this.tree.getSelection(); + const item = selection.length === 1 ? selection[0] : undefined; + // eslint-disable-next-line eqeqeq + if (item == null) { + return; + } + + if (isTimelineItem(item)) { + if (item.command) { + this.commandService.executeCommand(item.command.id, ...(item.command.arguments || [])); + } + } + else if (isLoadMoreCommand(item)) { + item.loading = true; + this.tree.rerender(item); + + 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; -export class TimelineElementTemplate { - static readonly id = 'TimelineElementTemplate'; + this.setLoadingUriMessage(); - constructor( - readonly container: HTMLElement, - readonly iconLabel: IconLabel, - readonly icon: HTMLElement - ) { } -} + return false; + } -export class TimelineIdentityProvider implements IIdentityProvider { - getId(item: TimelineItem): { toString(): string } { - return `${item.id}|${item.timestamp}`; + 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; + } + const event: UIEvent = treeEvent.browserEvent; + + event.preventDefault(); + event.stopPropagation(); + + if (!this.ensureValidItems()) { + return; + } + + this.tree.setFocus([item]); + const actions = commands.getItemContextActions(item); + if (!actions.length) { + return; + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => treeEvent.anchor, + getActions: () => actions, + getActionViewItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return undefined; + }, + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this.tree.domFocus(); + } + }, + getActionsContext: (): TimelineActionContext => ({ uri: this.uri, item: item }), + actionRunner: new TimelineActionRunner() + }); } } -export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { - getKeyboardNavigationLabel(element: TimelineItem): { toString(): string } { +export class TimelineElementTemplate implements IDisposable { + static readonly id = 'TimelineElementTemplate'; + + readonly actionBar: ActionBar; + readonly icon: HTMLElement; + readonly iconLabel: IconLabel; + readonly timestamp: HTMLSpanElement; + + constructor( + readonly container: HTMLElement, + actionViewItemProvider: IActionViewItemProvider + ) { + DOM.addClass(container, 'custom-view-tree-node-item'); + this.icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); + + this.iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true }); + + const timestampContainer = DOM.append(this.iconLabel.element, DOM.$('.timeline-timestamp-container')); + this.timestamp = DOM.append(timestampContainer, DOM.$('span.timeline-timestamp')); + + const actionsContainer = DOM.append(this.iconLabel.element, DOM.$('.actions')); + this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: actionViewItemProvider }); + } + + dispose() { + this.iconLabel.dispose(); + this.actionBar.dispose(); + } + + reset() { + this.icon.className = ''; + this.icon.style.backgroundImage = ''; + this.actionBar.clear(); + } +} + +export class TimelineIdentityProvider implements IIdentityProvider { + getId(item: TreeElement): { toString(): string } { + return item.handle; + } +} + +class TimelineActionRunner extends ActionRunner { + + runAction(action: IAction, { uri, item }: TimelineActionContext): Promise { + if (!isTimelineItem(item)) { + // TODO@eamodio do we need to do anything else? + return action.run(); + } + + return action.run(...[ + { + $mid: 11, + handle: item.handle, + source: item.source, + uri: uri + }, + uri, + item.source, + ]); + } +} + +export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + getKeyboardNavigationLabel(element: TreeElement): { toString(): string } { return element.label; } } -export class TimelineListVirtualDelegate implements IListVirtualDelegate { - getHeight(_element: TimelineItem): number { +export class TimelineListVirtualDelegate implements IListVirtualDelegate { + getHeight(_element: TreeElement): number { return 22; } - getTemplateId(element: TimelineItem): string { + getTemplateId(element: TreeElement): string { return TimelineElementTemplate.id; } } @@ -353,14 +1035,25 @@ export class TimelineListVirtualDelegate implements IListVirtualDelegate { readonly templateId: string = TimelineElementTemplate.id; - constructor(@IThemeService private _themeService: IThemeService) { } + private actionViewItemProvider: IActionViewItemProvider; + + constructor( + private readonly commands: TimelinePaneCommands, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IThemeService private themeService: IThemeService + ) { + this.actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction + ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) + : undefined; + } + + private uri: URI | undefined; + setUri(uri: URI | undefined) { + this.uri = uri; + } renderTemplate(container: HTMLElement): TimelineElementTemplate { - DOM.addClass(container, 'custom-view-tree-node-item'); - const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); - - const iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true }); - return new TimelineElementTemplate(container, iconLabel, icon); + return new TimelineElementTemplate(container, this.actionViewItemProvider); } renderElement( @@ -369,30 +1062,163 @@ 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(); + } + + getItemActions(element: TreeElement): IAction[] { + return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).primary; + } + + 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 scoped = this.contextKeyService.createScoped(); + scoped.createKey('view', this.pane.id); + scoped.createKey(context.key, context.value); + + 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(); + 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 65fe341ffc0..8f2823c6b7b 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -15,10 +15,16 @@ 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; + + id?: string; timestamp: number; label: string; - id?: string; + ariaLabel?: string; icon?: URI, iconDark?: URI, themeIcon?: { id: string }, @@ -26,21 +32,45 @@ export interface TimelineItem { detail?: string; command?: Command; contextValue?: string; -} -export interface TimelineItemWithSource extends TimelineItem { - source: string; + relativeTime?: string; + hideRelativeTime?: boolean; } export interface TimelineChangeEvent { id: string; - uri?: URI; + uri: URI | undefined; + reset: boolean +} + +export interface TimelineOptions { + cursor?: string; + limit?: number | { timestamp: number; id?: string }; +} + +export interface InternalTimelineOptions { + cacheResults: boolean; + resetCache: boolean; +} + +export interface Timeline { + source: string; + items: TimelineItem[]; + + paging?: { + cursor: string | undefined; + } } export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable { onDidChange?: Event; - provideTimeline(uri: URI, token: CancellationToken): Promise; + provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise; +} + +export interface TimelineSource { + id: string; + label: string; } export interface TimelineProviderDescriptor { @@ -55,7 +85,8 @@ export interface TimelineProvidersChangeEvent { } export interface TimelineRequest { - readonly items: Promise; + readonly result: Promise; + readonly options: TimelineOptions; readonly source: string; readonly tokenSource: CancellationTokenSource; readonly uri: URI; @@ -66,15 +97,16 @@ export interface ITimelineService { onDidChangeProviders: Event; onDidChangeTimeline: Event; + onDidChangeUri: Event; registerTimelineProvider(provider: TimelineProvider): IDisposable; unregisterTimelineProvider(id: string): void; - getSources(): string[]; + getSources(): TimelineSource[]; - getTimeline(uri: URI, token: CancellationToken): Promise; + getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: InternalTimelineOptions): TimelineRequest | undefined; - getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource): TimelineRequest | undefined; + 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 eda52441831..4fd45ce01ac 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event, Emitter } from 'vs/base/common/event'; 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, TimelineProvider, TimelineItem, TimelineChangeEvent, TimelineProvidersChangeEvent } 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,107 +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 _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 })); } - async getTimeline(uri: URI, token: CancellationToken, predicate?: (provider: TimelineProvider) => boolean) { - this.logService.trace(`TimelineService#getTimeline(${uri.toString(true)})`); - - const requests: Promise<[string, TimelineItem[]]>[] = []; - - for (const provider of this._providers.values()) { - if (typeof provider.scheme === 'string') { - if (provider.scheme !== '*' && provider.scheme !== uri.scheme) { - continue; - } - } else if (!provider.scheme.includes(uri.scheme)) { - continue; - } - if (!(predicate?.(provider) ?? true)) { - continue; - } - - requests.push(provider.provideTimeline(uri, token).then(p => [provider.id, p])); - } - - const timelines = await Promise.all(requests); - - const timeline = []; - for (const [source, items] of timelines) { - if (items.length === 0) { - continue; - } - - timeline.push(...items.map(item => ({ ...item, source: source }))); - } - - timeline.sort((a, b) => b.timestamp - a.timestamp); - return timeline; - } - - getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource) { + 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; } @@ -133,13 +178,18 @@ export class TimelineService implements ITimelineService { } return { - items: provider.provideTimeline(uri, tokenSource.token) - .then(items => { - items = items.map(item => ({ ...item, source: provider.id })); - items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })); + result: provider.provideTimeline(uri, options, tokenSource.token, internalOptions) + .then(result => { + if (result === undefined) { + return undefined; + } - return items; + result.items = result.items.map(item => ({ ...item, source: provider.id })); + result.items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })); + + return result; }), + options: options, source: provider.id, tokenSource: tokenSource, uri: uri @@ -151,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(); @@ -162,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] }); } }; @@ -179,12 +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] }); } + + 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..d5ba252cc6a 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(), @@ -164,8 +164,8 @@ export class ReleaseNotesManager { .then(undefined, onUnexpectedError); } - private async addGAParameters(uri: URI, origin: string, experiment = '1'): Promise { - if (this._environmentService.isBuilt && !this._environmentService.isExtensionDevelopment && !this._environmentService.args['disable-telemetry'] && !!this._productService.enableTelemetry) { + private async addGAParameters(uri: URI, origin: string, experiment = '1'): Promise { + if (this._environmentService.isBuilt && !this._environmentService.isExtensionDevelopment && !this._environmentService.disableTelemetry && !!this._productService.enableTelemetry) { if (uri.scheme === 'https' && uri.authority === 'code.visualstudio.com') { const info = await this._telemetryService.getTelemetryInfo(); @@ -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..f1b70d876bf 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); @@ -137,7 +137,7 @@ export class ProductContribution implements IWorkbenchContribution { // was there an update? if so, open release notes const releaseNotesUrl = productService.releaseNotesUrl; - if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion) { + if (shouldShowReleaseNotes && releaseNotesUrl && lastVersion && productService.version !== lastVersion) { showReleaseNotes(instantiationService, productService.version) .then(undefined, () => { notificationService.prompt( @@ -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/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index af5564e4063..b1b2535e52a 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -34,8 +34,10 @@ export class OpenUrlAction extends Action { run(): Promise { return this.quickInputService.input({ prompt: 'URL to open' }).then(input => { - const uri = URI.parse(input); - this.urlService.open(uri, { trusted: true }); + if (input) { + const uri = URI.parse(input); + this.urlService.open(uri, { trusted: true }); + } }); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/media/check-dark.svg b/src/vs/workbench/contrib/userDataSync/browser/media/check-dark.svg deleted file mode 100644 index 2d16f390078..00000000000 --- a/src/vs/workbench/contrib/userDataSync/browser/media/check-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/userDataSync/browser/media/check-light.svg b/src/vs/workbench/contrib/userDataSync/browser/media/check-light.svg deleted file mode 100644 index a9f8aa131b5..00000000000 --- a/src/vs/workbench/contrib/userDataSync/browser/media/check-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts index 5148c307b3c..7e035fddbce 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService.ts @@ -3,33 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IUserDataSyncService, IUserDataSyncLogService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { constructor( @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService userDataSyncService: IUserDataSyncService, - @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService, + @IAuthenticationTokenService authTokenService: IAuthenticationTokenService, @IInstantiationService instantiationService: IInstantiationService, @IHostService hostService: IHostService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService); + super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService, telemetryService); - // Sync immediately if there is a local change. - this._register(Event.debounce(Event.any( - userDataSyncService.onDidChangeLocal, + this._register(Event.debounce(Event.any( + Event.map(hostService.onDidChangeFocus, () => 'windowFocus'), instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync, - hostService.onDidChangeFocus - ), () => undefined, 500)(() => this.triggerAutoSync())); + userDataSyncService.onDidChangeLocal, + ), (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerAutoSync(sources))); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 5bd817b28e6..d215668d3f0 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -3,48 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; 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 { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IUserDataSyncEnablementService, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; - -class UserDataSyncSettingsMigrationContribution implements IWorkbenchContribution { - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, - ) { - if (getUserDataSyncStore(configurationService)) { - if (!configurationService.getValue('sync.enableSettings')) { - userDataSyncEnablementService.setResourceEnablement('settings', false); - } - if (!configurationService.getValue('sync.enableKeybindings')) { - userDataSyncEnablementService.setResourceEnablement('keybindings', false); - } - if (!configurationService.getValue('sync.enableUIState')) { - userDataSyncEnablementService.setResourceEnablement('globalState', false); - } - if (!configurationService.getValue('sync.enableExtensions')) { - userDataSyncEnablementService.setResourceEnablement('extensions', false); - } - if (configurationService.getValue('sync.enable')) { - userDataSyncEnablementService.setEnablement(true); - } - this.removeFromConfiguration(); - } - } - - private async removeFromConfiguration(): Promise { - await this.configurationService.updateValue('sync.enable', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableSettings', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableKeybindings', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableUIState', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableExtensions', undefined, ConfigurationTarget.USER); - } -} +import { UserDataSyncViewContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncView'; const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Ready); -workbenchRegistry.registerWorkbenchContribution(UserDataSyncSettingsMigrationContribution, 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 6af68ea3feb..b79b651d6ee 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,66 +4,66 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; 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 } from 'vs/editor/browser/editorExtensions'; +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 { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; -import { IMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey'; +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 } from 'vs/platform/quickinput/common/quickInput'; +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, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { + CONTEXT_SYNC_STATE, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, 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'; import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; -import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; +import { IActivityService, IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +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 { UserDataSyncAuthentication, IUserDataSyncAuthentication, AuthStatus, CONTEXT_AUTH_TOKEN_STATE } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncAuthentication'; -const enum AuthStatus { - Initializing = 'Initializing', - SignedIn = 'SignedIn', - SignedOut = 'SignedOut', - Unavailable = 'Unavailable' -} -const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); -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 }; +type ConfigureSyncQuickPickItem = { id: SyncResource, label: string, description?: string }; -function getSyncAreaLabel(source: SyncSource): 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"); } } @@ -76,22 +76,48 @@ type FirstTimeSyncClassification = { action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; +const getActivityTitle = (label: string, userDataSyncService: IUserDataSyncService): string => { + if (userDataSyncService.status === SyncStatus.Syncing) { + return localize('sync is on with syncing', "{0} (syncing)", label); + } + if (userDataSyncService.lastSyncTime) { + return localize('sync is on with time', "{0} (synced {1})", label, fromNow(userDataSyncService.lastSyncTime, true)); + } + return label; +}; +const getIdentityTitle = (label: string, providerDisplayName?: string, accountName?: string) => { + return accountName ? `${label} (${providerDisplayName}:${accountName})` : label; +}; +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(providerName: string | undefined, accountName: string | undefined) { return getIdentityTitle(localize('stop sync', "Preferences Sync: Turn Off"), providerName, accountName); } }; +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', "Preferences Sync: Show Log"), userDataSyncService); + } +}; +const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Preferences Sync: Show Settings"), }; + export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { - private readonly userDataSyncStore: IUserDataSyncStore | undefined; private readonly syncEnablementContext: IContextKey; private readonly syncStatusContext: IContextKey; - private readonly authenticationState: IContextKey; + private readonly conflictsSources: IContextKey; - private readonly conflictsDisposables = new Map(); + private readonly badgeDisposable = this._register(new MutableDisposable()); private readonly signInNotificationDisposable = this._register(new MutableDisposable()); - private _activeAccount: AuthenticationSession | undefined; + + private readonly userDataSyncAuthentication: IUserDataSyncAuthentication | undefined; constructor( @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IAuthenticationService authenticationService: IAuthenticationService, @IContextKeyService contextKeyService: IContextKeyService, @IActivityService private readonly activityService: IActivityService, @INotificationService private readonly notificationService: INotificationService, @@ -102,35 +128,48 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService instantiationService: IInstantiationService, @IOutputService private readonly outputService: IOutputService, - @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, + @IAuthenticationTokenService 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, ) { super(); - this.userDataSyncStore = getUserDataSyncStore(configurationService); + const userDataSyncStore = getUserDataSyncStore(productService, configurationService); this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); - this.authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService); + this.conflictsSources = CONTEXT_CONFLICTS_SOURCES.bindTo(contextKeyService); - if (this.userDataSyncStore) { + if (userDataSyncStore) { registerConfiguration(); + this.userDataSyncAuthentication = new UserDataSyncAuthentication(userDataSyncStore.authenticationProviderId, + authenticationService, + contextKeyService, + this.notificationService, + quickInputService, + authTokenService, + telemetryService, + storageService, + productService, + userDataSyncEnablementService); + 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.userDataSyncEnablementService.onDidChangeEnablement(enabled => this.onDidChangeEnablement(enabled))); - this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); - this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); - this._register(this.authenticationService.onDidChangeSessions(e => this.onDidChangeSessions(e))); - this._register(userDataAutoSyncService.onError(({ code, source }) => this.onAutoSyncError(code, source))); + this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error))); + this._register(this.userDataSyncAuthentication.onDidChangeActiveAccount(_ => this.updateBadge())); + this._register(this.userDataSyncAuthentication.onAccountsAvailable(_ => this.turnOn(true))); this.registerActions(); - this.initializeActiveAccount().then(_ => { + this.userDataSyncAuthentication.initializeActiveAccount().then(_ => { if (!isWeb) { - this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => userDataAutoSyncService.triggerAutoSync())); + this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(source => userDataAutoSyncService.triggerAutoSync([source]))); } }); @@ -139,149 +178,65 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private async initializeActiveAccount(): Promise { - const sessions = await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId); - // Auth provider has not yet been registered - if (!sessions) { - return; - } - - if (sessions.length === 0) { - await this.setActiveAccount(undefined); - return; - } - - if (sessions.length === 1) { - this.logAuthenticatedEvent(sessions[0]); - await this.setActiveAccount(sessions[0]); - 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); - } - } - - private logAuthenticatedEvent(session: AuthenticationSession): void { - type UserAuthenticatedClassification = { - id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' }; - }; - - type UserAuthenticatedEvent = { - id: string; - }; - - const id = session.id.split('/')[1]; - this.telemetryService.publicLog2('user.authenticated', { id }); - } - - get activeAccount(): AuthenticationSession | undefined { - return this._activeAccount; - } - - async setActiveAccount(account: AuthenticationSession | undefined) { - this._activeAccount = account; - - if (account) { - try { - const token = await account.accessToken(); - this.userDataAuthTokenService.setToken(token); - this.authenticationState.set(AuthStatus.SignedIn); - } catch (e) { - this.userDataAuthTokenService.setToken(undefined); - this.authenticationState.set(AuthStatus.Unavailable); - } - } else { - this.userDataAuthTokenService.setToken(undefined); - this.authenticationState.set(AuthStatus.SignedOut); - } - - this.updateBadge(); - } - - private async onDidChangeSessions(providerId: string): Promise { - if (providerId === this.userDataSyncStore!.authenticationProviderId) { - 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); - } else { - this.initializeActiveAccount(); - } - } - } - - private async onDidRegisterAuthenticationProvider(providerId: string) { - if (providerId === this.userDataSyncStore!.authenticationProviderId) { - await this.initializeActiveAccount(); - } - } - - private onDidUnregisterAuthenticationProvider(providerId: string) { - if (providerId === this.userDataSyncStore!.authenticationProviderId) { - this.setActiveAccount(undefined); - this.authenticationState.reset(); - } - } - private onDidChangeSyncStatus(status: SyncStatus) { this.syncStatusContext.set(status); - if (status === SyncStatus.Syncing) { - // Show syncing progress if takes more than 1s. - timeout(1000).then(() => this.updateBadge()); - } else { - this.updateBadge(); - } + this.updateBadge(); } - 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); - const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea), + 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 }); } } ], @@ -289,18 +244,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); })); } } @@ -312,29 +267,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); @@ -345,13 +295,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.syncEnablementContext.set(enabled); this.updateBadge(); if (enabled) { - if (this.authenticationState.get() === AuthStatus.SignedOut) { - const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId); - const handle = this.notificationService.prompt(Severity.Info, localize('sign in message', "Please sign in with your {0} account to continue sync", displayName), + if (this.userDataSyncAuthentication?.authenticationState === AuthStatus.SignedOut) { + const handle = this.notificationService.prompt(Severity.Info, localize('sign in message', "Please sign in with your {0} account to continue sync", this.userDataSyncAuthentication?.providerDisplayName), [ { label: localize('Sign in', "Sign in"), - run: () => this.signIn() + run: () => this.userDataSyncAuthentication!.login() } ]); this.signInNotificationDisposable.value = toDisposable(() => handle.close()); @@ -362,24 +311,92 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void { - switch (code) { + private onAutoSyncError(error: UserDataSyncError): void { + switch (error.code) { + case UserDataSyncErrorCode.TurnedOff: + case UserDataSyncErrorCode.SessionExpired: + this.notificationService.notify({ + severity: Severity.Info, + message: localize('turned off', "Sync was turned off from another device."), + actions: { + primary: [new Action('turn on sync', localize('turn on sync', "Turn on Sync"), undefined, true, () => this.turnOn())] + } + }); + return; case UserDataSyncErrorCode.TooLarge: - if (source === SyncSource.Keybindings || source === SyncSource.Settings) { - const sourceArea = getSyncAreaLabel(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 synchronizing {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, sourceArea, '100kb'), + 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', "Show {0} file", sourceArea), undefined, true, - () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + primary: [new Action('open sync file', localize('open file', "Open {0} File", sourceArea), undefined, true, + () => error.resource === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); } return; + case UserDataSyncErrorCode.Incompatible: + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('error incompatible', "Turned off sync because local data is incompatible with the data in the cloud. Please update {0} and turn on sync to continue syncing.", this.productService.nameLong), + }); + return; } } + private readonly invalidContentErrorDisposables = new Map(); + private onSyncErrors(errors: [SyncResource, UserDataSyncError][]): void { + if (errors.length) { + for (const [source, error] of errors) { + switch (error.code) { + case UserDataSyncErrorCode.LocalInvalidContent: + this.handleInvalidContentError(source); + break; + default: + const disposable = this.invalidContentErrorDisposables.get(source); + if (disposable) { + disposable.dispose(); + this.invalidContentErrorDisposables.delete(source); + } + } + } + } else { + this.invalidContentErrorDisposables.forEach(disposable => disposable.dispose()); + this.invalidContentErrorDisposables.clear(); + } + } + + private handleInvalidContentError(source: SyncResource): void { + if (this.invalidContentErrorDisposables.has(source)) { + return; + } + if (source !== SyncResource.Settings && source !== SyncResource.Keybindings) { + return; + } + 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; + } + const errorArea = getSyncAreaLabel(source); + const handle = this.notificationService.notify({ + severity: Severity.Error, + 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 === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + } + }); + this.invalidContentErrorDisposables.set(source, toDisposable(() => { + // close the error warning notification + handle.close(); + this.invalidContentErrorDisposables.delete(source); + })); + } + private async updateBadge(): Promise { this.badgeDisposable.clear(); @@ -387,14 +404,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo let clazz: string | undefined; let priority: number | undefined = undefined; - if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncEnablementService.isEnabled() && this.authenticationState.get() === AuthStatus.SignedOut) { + if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncAuthentication?.authenticationState === 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.status === SyncStatus.Syncing) { - badge = new ProgressBadge(() => localize('syncing', "Synchronizing User Configuration...")); - clazz = 'progress-badge'; - priority = 1; + } 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) { @@ -402,19 +415,47 @@ 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, + localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."), + [ + localize('open doc', "Open Documentation"), + localize('turn on sync', "Turn on Sync"), + localize('cancel', "Cancel"), + ], + { + cancelId: 2 + } + ); + switch (result.choice) { + 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 sync', "Turn on Sync"); + quickPick.title = localize('turn on title', "Preferences Sync: Turn On"); quickPick.ok = false; quickPick.customButton = true; - if (this.authenticationState.get() === AuthStatus.SignedIn) { - quickPick.customLabel = localize('turn on', "Turn on"); + if (this.userDataSyncAuthentication?.authenticationState === AuthStatus.SignedIn) { + quickPick.customLabel = localize('turn on', "Turn On"); } else { - const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId); - quickPick.description = localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", displayName); + const displayName = this.userDataSyncAuthentication?.providerDisplayName; + quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName); quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on"); } quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync"); @@ -426,7 +467,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(); } })); @@ -435,28 +476,39 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } - private async doTurnOn(): Promise { - if (this.authenticationState.get() === AuthStatus.SignedOut) { - await this.signIn(); + private async doTurnOn(skipAccountPick?: boolean): Promise { + // If this was not triggered by signing in from the accounts menu, show accounts list + if (!skipAccountPick) { + await this.userDataSyncAuthentication!.confirmActiveAccount(); } + + // User did not pick an account or login failed, no need to continue + if (this.userDataSyncAuthentication!.authenticationState !== AuthStatus.SignedIn) { + return; + } + await this.handleFirstTimeSync(); this.userDataSyncEnablementService.setEnablement(true); + 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), }]; } @@ -475,7 +527,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; @@ -504,15 +556,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } const result = await this.dialogService.show( Severity.Info, - localize('firs time sync', "First time Sync"), + localize('firs time sync', "Sync"), [ localize('merge', "Merge"), localize('cancel', "Cancel"), - localize('replace', "Replace (Overwrite Local)"), + localize('replace', "Replace Local"), ], { cancelId: 1, - detail: localize('first time sync detail', "Synchronizing from this device for the first time.\nWould you like to merge or replace with the data from the cloud?"), + detail: localize('first time sync detail', "It looks like this is the first time sync is set up.\nWould you like to merge or replace with the data from the cloud?"), } ); switch (result.choice) { @@ -532,82 +584,74 @@ 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"), + primaryButton: localize('turn off', "Turn Off"), checkbox: { label: localize('turn off sync everywhere', "Turn off sync on all your devices and clear the data from the cloud.") } }); if (result.confirmed) { - await this.disableSync(); if (result.checkboxChecked) { this.telemetryService.publicLog2('sync/turnOffEveryWhere'); await this.userDataSyncService.reset(); } else { await this.userDataSyncService.resetLocal(); } + this.disableSync(); } } - 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 { - await this.setActiveAccount(await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access'])); - } catch (e) { - this.notificationService.error(e); - throw e; - } - } - private async signOut(): Promise { - if (this.activeAccount) { - await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id); - await this.setActiveAccount(undefined); - } - } - 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.getResource()))[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.getResource() : input.getResource(); - return isEqual(resource, this.workbenchEnvironmentService.settingsSyncPreviewResource) || isEqual(resource, this.workbenchEnvironmentService.keybindingsSyncPreviewResource); + const resource = input instanceof DiffEditorInput ? input.master.resource : input.resource; + 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, @@ -618,15 +662,27 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private showSyncLog(): Promise { + private showSyncActivity(): Promise { return this.outputService.showChannel(Constants.userDataSyncLogChannelId); } private registerActions(): void { + this.registerTurnOnSyncAction(); + this.registerSignInAction(); + this.registerShowSettingsConflictsAction(); + this.registerShowKeybindingsConflictsAction(); + this.registerShowSnippetsConflictsAction(); + this.registerSyncStatusAction(); - const turnOnSyncCommandId = 'workbench.userData.actions.syncStart'; + this.registerTurnOffSyncAction(); + this.registerConfigureSyncAction(); + this.registerShowActivityAction(); + this.registerShowSettingsAction(); + } + + private registerTurnOnSyncAction(): void { const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthStatus.Initializing)); - CommandsRegistry.registerCommand(turnOnSyncCommandId, async () => { + CommandsRegistry.registerCommand(turnOnSyncCommand.id, async () => { try { await this.turnOn(); } catch (e) { @@ -638,135 +694,282 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: turnOnSyncCommandId, - title: localize('global activity turn on sync', "Turn on Sync...") + id: turnOnSyncCommand.id, + title: localize('global activity turn on sync', "Turn on Preferences Sync...") }, when: turnOnSyncWhenContext, + order: 1 }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: turnOnSyncCommandId, - title: localize('turn on sync...', "Sync: Turn on Sync...") - }, + command: turnOnSyncCommand, when: turnOnSyncWhenContext, }); - - const signInCommandId = 'workbench.userData.actions.signin'; - const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)); - CommandsRegistry.registerCommand(signInCommandId, () => this.signIn()); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { group: '5_sync', command: { - id: signInCommandId, - title: localize('global activity sign in', "Sign in to Sync... (1)") + id: turnOnSyncCommand.id, + title: localize('global activity turn on sync', "Turn on Preferences Sync...") }, - when: signInWhenContext, - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: signInCommandId, - title: localize('sign in', "Sync: Sign in to sync...") - }, - when: signInWhenContext, - }); - - const stopSyncCommandId = 'workbench.userData.actions.stopSync'; - CommandsRegistry.registerCommand(stopSyncCommandId, () => this.turnOff()); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_sync', - command: { - id: stopSyncCommandId, - title: localize('global activity stop sync', "Turn off Sync") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: stopSyncCommandId, - title: localize('stop sync', "Sync: Turn off Sync") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), - }); - - const resolveSettingsConflictsCommandId = 'workbench.userData.actions.resolveSettingsConflicts'; - const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); - CommandsRegistry.registerCommand(resolveSettingsConflictsCommandId, () => this.handleConflicts(SyncSource.Settings)); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_sync', - command: { - id: resolveSettingsConflictsCommandId, - title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), - }, - when: resolveSettingsConflictsWhenContext, - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: resolveSettingsConflictsCommandId, - title: localize('showConflicts', "Sync: Show Settings Conflicts"), - }, - when: resolveSettingsConflictsWhenContext, - }); - - const resolveKeybindingsConflictsCommandId = 'workbench.userData.actions.resolveKeybindingsConflicts'; - const resolveKeybindingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); - CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommandId, () => this.handleConflicts(SyncSource.Keybindings)); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_sync', - command: { - id: resolveKeybindingsConflictsCommandId, - title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), - }, - when: resolveKeybindingsConflictsWhenContext, - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: resolveKeybindingsConflictsCommandId, - title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts"), - }, - when: resolveKeybindingsConflictsWhenContext, - }); - - const signOutMenuItem: IMenuItem = { - group: '5_sync', - command: { - id: 'workbench.userData.actions.signout', - title: localize('sign out', "Sync: Sign out") - }, - when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn)), - }; - CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem); - - const configureSyncCommandId = 'workbench.userData.actions.configureSync'; - CommandsRegistry.registerCommand(configureSyncCommandId, () => this.configureSyncOptions()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: configureSyncCommandId, - title: localize('configure sync', "Sync: Configure") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), - }); - - const showSyncLogCommandId = 'workbench.userData.actions.showSyncLog'; - CommandsRegistry.registerCommand(showSyncLogCommandId, () => this.showSyncLog()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: showSyncLogCommandId, - title: localize('show sync log', "Sync: Show Sync Log") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), - }); - - const resetLocalCommandId = 'workbench.userData.actions.resetLocal'; - CommandsRegistry.registerCommand(resetLocalCommandId, () => this.userDataSyncService.resetLocal()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: resetLocalCommandId, - title: localize('reset local', "Developer: Reset Local (Sync)") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), + when: turnOnSyncWhenContext, }); } + + private registerSignInAction(): void { + const that = this; + this._register(registerAction2(class StopSyncAction extends Action2 { + constructor() { + super({ + id: signInCommand.id, + title: localize('sign in 2', "Preferences Sync: Sign in to sync (1)"), + menu: { + group: '5_sync', + id: MenuId.GlobalActivity, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)), + order: 2 + }, + }); + } + async run(): Promise { + try { + await that.userDataSyncAuthentication?.login(); + } catch (e) { + that.notificationService.error(e); + } + } + })); + } + + private registerShowSettingsConflictsAction(): void { + const resolveSettingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); + CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Settings)); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: { + id: resolveSettingsConflictsCommand.id, + title: localize('resolveConflicts_global', "Preferences Sync: Show Settings Conflicts (1)"), + }, + when: resolveSettingsConflictsWhenContext, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: resolveSettingsConflictsCommand.id, + title: localize('resolveConflicts_global', "Preferences Sync: Show Settings Conflicts (1)"), + }, + when: resolveSettingsConflictsWhenContext, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: resolveSettingsConflictsCommand, + when: resolveSettingsConflictsWhenContext, + }); + } + + private registerShowKeybindingsConflictsAction(): void { + const resolveKeybindingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); + CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Keybindings)); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: { + id: resolveKeybindingsConflictsCommand.id, + title: localize('resolveKeybindingsConflicts_global', "Preferences Sync: Show Keybindings Conflicts (1)"), + }, + when: resolveKeybindingsConflictsWhenContext, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: resolveKeybindingsConflictsCommand.id, + title: localize('resolveKeybindingsConflicts_global', "Preferences Sync: Show Keybindings Conflicts (1)"), + }, + when: resolveKeybindingsConflictsWhenContext, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + 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 { + const that = this; + const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)); + this._register(registerAction2(class SyncStatusAction extends Action2 { + constructor() { + super({ + id: 'workbench.userData.actions.syncStatus', + title: localize('sync is on', "Preferences Sync is On"), + menu: [ + { + id: MenuId.GlobalActivity, + group: '5_sync', + when, + order: 3 + }, + { + id: MenuId.MenubarPreferencesMenu, + group: '5_sync', + when, + order: 3, + } + ], + }); + } + run(accessor: ServicesAccessor): any { + return new Promise((c, e) => { + const quickInputService = accessor.get(IQuickInputService); + const commandService = accessor.get(ICommandService); + const disposables = new DisposableStore(); + const quickPick = quickInputService.createQuickPick(); + disposables.add(quickPick); + const items: Array = []; + 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 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' }); + } + items.push({ id: configureSyncCommand.id, label: configureSyncCommand.title }); + items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title }); + items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title(that.userDataSyncService) }); + items.push({ type: 'separator' }); + items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title(that.userDataSyncAuthentication!.providerDisplayName, that.userDataSyncAuthentication!.activeAccountName) }); + quickPick.items = items; + disposables.add(quickPick.onDidAccept(() => { + if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) { + commandService.executeCommand(quickPick.selectedItems[0].id); + } + quickPick.hide(); + })); + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + c(); + })); + quickPick.show(); + }); + } + })); + } + + private registerTurnOffSyncAction(): void { + const that = this; + this._register(registerAction2(class StopSyncAction extends Action2 { + constructor() { + super({ + id: stopSyncCommand.id, + title: stopSyncCommand.title(that.userDataSyncAuthentication!.providerDisplayName, that.userDataSyncAuthentication!.activeAccountName), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), + }, + }); + } + async run(): Promise { + try { + await that.turnOff(); + } catch (e) { + if (!isPromiseCanceledError(e)) { + that.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e))); + } + } + } + })); + } + + private registerConfigureSyncAction(): void { + const that = this; + this._register(registerAction2(class ShowSyncActivityAction extends Action2 { + constructor() { + super({ + id: configureSyncCommand.id, + title: configureSyncCommand.title, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), + }, + }); + } + run(): any { return that.configureSyncOptions(); } + })); + } + + private registerShowActivityAction(): void { + const that = this; + this._register(registerAction2(class ShowSyncActivityAction extends Action2 { + constructor() { + super({ + id: showSyncActivityCommand.id, + get title() { return showSyncActivityCommand.title(that.userDataSyncService); }, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), + }, + }); + } + run(): any { return that.showSyncActivity(); } + })); + } + + private registerShowSettingsAction(): void { + this._register(registerAction2(class ShowSyncSettingsAction extends Action2 { + constructor() { + super({ + id: showSyncSettingsCommand.id, + title: showSyncSettingsCommand.title, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), + }, + }); + } + run(accessor: ServicesAccessor): any { + accessor.get(IPreferencesService).openGlobalSettings(false, { query: '@tag:sync' }); + } + })); + } + } class UserDataRemoteContentProvider implements ITextModelContentProvider { @@ -779,15 +982,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; } @@ -806,7 +1002,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, @@ -820,7 +1015,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())); } @@ -839,11 +1035,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'); } @@ -853,32 +1054,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, syncAreaLabel) - : localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel, syncAreaLabel), + ? 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 { @@ -893,6 +1096,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/userDataSyncAuthentication.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAuthentication.ts new file mode 100644 index 00000000000..f68aeea283a --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAuthentication.ts @@ -0,0 +1,350 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { localize } from 'vs/nls'; +import { distinct } from 'vs/base/common/arrays'; +import Severity from 'vs/base/common/severity'; +import { Action } from 'vs/base/common/actions'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; + +export interface IUserDataSyncAuthenticationService { + _serviceBrand: undefined; +} + +export const enum AuthStatus { + Initializing = 'Initializing', + SignedIn = 'SignedIn', // Signed in indicates that there is an active account + SignedOut = 'SignedOut', + Unavailable = 'Unavailable' +} + +export const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthStatus.Initializing); +const USER_DATA_SYNC_ACCOUNT_PREFERENCE_KEY = 'userDataSyncAccountPreference'; + +export interface IUserDataSyncAuthentication { + providerDisplayName: string | undefined; + activeAccountName: string | undefined; + authenticationState: AuthStatus; + + initializeActiveAccount(): Promise; + confirmActiveAccount(): Promise; + + login(): Promise; + logout(): Promise; + + readonly onAccountsAvailable: Event; + readonly onDidChangeActiveAccount: Event; +} +export class UserDataSyncAuthentication extends Disposable implements IUserDataSyncAuthentication { + private readonly _authenticationState: IContextKey; + private _activeAccount: AuthenticationSession | undefined; + private loginInProgress: boolean = false; + + private _onDidChangeActiveAccount: Emitter = this._register(new Emitter()); + readonly onDidChangeActiveAccount: Event = this._onDidChangeActiveAccount.event; + + private _onAccountsAvailable: Emitter = this._register(new Emitter()); + readonly onAccountsAvailable: Event = this._onAccountsAvailable.event; + + constructor( + private readonly _authenticationProviderId: string, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IContextKeyService contextKeyService: IContextKeyService, + @INotificationService private readonly notificationService: INotificationService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IStorageService private readonly storageService: IStorageService, + @IProductService readonly productService: IProductService, + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService + ) { + super(); + this._authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService); + this._register(this.authTokenService.onTokenFailed(_ => this.onTokenFailed())); + this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); + this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); + this._register(this.authenticationService.onDidChangeSessions(e => this.onDidChangeSessions(e))); + } + + get providerDisplayName(): string | undefined { + try { + return this.authenticationService.getDisplayName(this._authenticationProviderId); + } catch (e) { + // Ignore, provider is not yet registered + return undefined; + } + } + + get authenticationState(): AuthStatus { + return this._authenticationState.get()!; + } + + get activeAccountName(): string | undefined { + return this.activeAccount?.accountName; + } + + async initializeActiveAccount(): Promise { + if (!this.userDataSyncEnablementService.isEnabled()) { + return; + } + + const sessions = await this.authenticationService.getSessions(this._authenticationProviderId); + // Auth provider has not yet been registered + if (!sessions) { + return; + } + + if (sessions.length === 0) { + await this.setActiveAccount(undefined); + return; + } + + if (sessions.length === 1) { + this.logAuthenticatedEvent(sessions[0]); + await this.setActiveAccount(sessions[0]); + return; + } + + 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); + } + + async confirmActiveAccount(): Promise { + const sessions = await this.authenticationService.getSessions(this._authenticationProviderId) || []; + if (sessions.length) { + await new Promise((resolve, _) => { + const disposables: DisposableStore = new DisposableStore(); + const quickPick = this.quickInputService.createQuickPick<{ id: string, label: string, session?: AuthenticationSession, detail?: string }>(); + disposables.add(quickPick); + + quickPick.title = localize('pick account', "{0}: Pick an account", this.providerDisplayName); + quickPick.ok = false; + quickPick.placeholder = localize('choose account placeholder', "Pick an account for syncing"); + quickPick.ignoreFocusOut = true; + + const chooseAnotherItemId = 'chooseAnother'; + const accountPreference = this.storageService.get(USER_DATA_SYNC_ACCOUNT_PREFERENCE_KEY, StorageScope.GLOBAL); + + // Move previously used account to first item + const orderedSessions = sessions.slice().sort(session => { + if (session.id === accountPreference) { + return -1; + } else { + return 0; + } + }); + + quickPick.items = orderedSessions.map(session => { + return { + id: session.id, + label: session.accountName, + session: session, + detail: session.id === accountPreference ? localize('previously used', "Previously used") : '' + }; + }).concat([{ + id: chooseAnotherItemId, + label: localize('choose another', "Use another account") + } as any]); + + disposables.add(quickPick.onDidAccept(async () => { + const selected = quickPick.selectedItems[0]; + if (selected) { + if (selected.id === chooseAnotherItemId) { + this.login(); + } else { + this.setActiveAccount(selected.session); + } + + quickPick.hide(); + resolve(); + } + })); + + disposables.add(quickPick.onDidHide(() => disposables.dispose())); + quickPick.show(); + }); + } else { + await this.login(); + } + } + + async login(): Promise { + try { + this.loginInProgress = true; + await this.setActiveAccount(await this.authenticationService.login(this._authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access'])); + this.loginInProgress = false; + } catch (e) { + this.notificationService.error(localize('loginFailed', "Logging in failed: {0}", e.message)); + } + } + + async logout(): Promise { + if (this.activeAccount) { + await this.authenticationService.logout(this._authenticationProviderId, this.activeAccount.id); + } + } + + private logAuthenticatedEvent(session: AuthenticationSession): void { + type UserAuthenticatedClassification = { + id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' }; + }; + + type UserAuthenticatedEvent = { + id: string; + }; + + const id = session.id.split('/')[1]; + this.telemetryService.publicLog2('user.authenticated', { id }); + } + + get activeAccount(): AuthenticationSession | undefined { + return this._activeAccount; + } + + async setActiveAccount(account: AuthenticationSession | undefined) { + this._activeAccount = account; + + if (account) { + try { + const token = await account.getAccessToken(); + this.authTokenService.setToken(token); + this.storageService.store(USER_DATA_SYNC_ACCOUNT_PREFERENCE_KEY, account.id, StorageScope.GLOBAL); + this._authenticationState.set(AuthStatus.SignedIn); + } catch (e) { + this.authTokenService.setToken(undefined); + this._authenticationState.set(AuthStatus.Unavailable); + } + } else { + this.authTokenService.setToken(undefined); + this._authenticationState.set(AuthStatus.SignedOut); + } + + this._onDidChangeActiveAccount.fire(); + } + + 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); + quickPick.dispose(); + resolve(); + }); + + quickPick.show(); + }); + } + + private async onDidChangeSessions(e: { providerId: string, event: AuthenticationSessionsChangeEvent }): Promise { + const { providerId, event } = e; + if (!this.userDataSyncEnablementService.isEnabled()) { + if (event.added) { + this._onAccountsAvailable.fire(); + } + return; + } + + if (providerId === this._authenticationProviderId) { + if (this.loginInProgress) { + return; + } + + if (this.activeAccount) { + 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('sign in', localize('sign in', "Sign in"), undefined, true, () => this.login())] + } + }); + return; + } + } + + if (event.added.length) { + // Offer to switch accounts + const accounts = (await this.authenticationService.getSessions(this._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._authenticationProviderId) || []); + const matchingAccount = accounts.filter(a => a.id === this.activeAccount?.id)[0]; + this.setActiveAccount(matchingAccount); + } + } + } else { + this.initializeActiveAccount(); + } + } + } + + private async onTokenFailed(): Promise { + if (this.activeAccount) { + const accounts = (await this.authenticationService.getSessions(this._authenticationProviderId) || []); + const matchingAccount = accounts.filter(a => a.id === this.activeAccount?.id)[0]; + this.setActiveAccount(matchingAccount); + } else { + this.setActiveAccount(undefined); + } + } + + private async onDidRegisterAuthenticationProvider(providerId: string) { + if (providerId === this._authenticationProviderId) { + await this.initializeActiveAccount(); + } + } + + private onDidUnregisterAuthenticationProvider(providerId: string) { + if (providerId === this._authenticationProviderId) { + this.setActiveAccount(undefined); + this._authenticationState.reset(); + } + } +} + diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts index 21dc9c85367..1642cd210a4 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger.ts @@ -16,8 +16,8 @@ import { IViewlet } from 'vs/workbench/common/viewlet'; export class UserDataSyncTrigger extends Disposable { - private readonly _onDidTriggerSync: Emitter = this._register(new Emitter()); - readonly onDidTriggerSync: Event = this._onDidTriggerSync.event; + private readonly _onDidTriggerSync: Emitter = this._register(new Emitter()); + readonly onDidTriggerSync: Event = this._onDidTriggerSync.event; constructor( @IEditorService editorService: IEditorService, @@ -25,37 +25,44 @@ export class UserDataSyncTrigger extends Disposable { @IViewletService viewletService: IViewletService, ) { super(); - this._register(Event.debounce(Event.any( - Event.filter(editorService.onDidActiveEditorChange, () => this.isUserDataEditorInput(editorService.activeEditor)), - Event.filter(viewletService.onDidViewletOpen, viewlet => this.isUserDataViewlet(viewlet)) - ), () => undefined, 500)(() => this._onDidTriggerSync.fire())); + this._register(Event.any( + Event.map(editorService.onDidActiveEditorChange, () => this.getUserDataEditorInputSource(editorService.activeEditor)), + Event.map(viewletService.onDidViewletOpen, viewlet => this.getUserDataViewletSource(viewlet)) + )(source => { + if (source) { + this._onDidTriggerSync.fire(source); + } + })); } - private isUserDataViewlet(viewlet: IViewlet): boolean { - return viewlet.getId() === VIEWLET_ID; + private getUserDataViewletSource(viewlet: IViewlet): string | undefined { + if (viewlet.getId() === VIEWLET_ID) { + return 'extensionsViewlet'; + } + return undefined; } - private isUserDataEditorInput(editorInput: IEditorInput | undefined): boolean { + private getUserDataEditorInputSource(editorInput: IEditorInput | undefined): string | undefined { if (!editorInput) { - return false; + return undefined; } if (editorInput instanceof SettingsEditor2Input) { - return true; + return 'settingsEditor'; } if (editorInput instanceof PreferencesEditorInput) { - return true; + return 'settingsEditor'; } if (editorInput instanceof KeybindingsEditorInput) { - return true; + return 'keybindingsEditor'; } - const resource = editorInput.getResource(); + const resource = editorInput.resource; if (isEqual(resource, this.workbenchEnvironmentService.settingsResource)) { - return true; + return 'settingsEditor'; } if (isEqual(resource, this.workbenchEnvironmentService.keybindingsResource)) { - return true; + return 'keybindingsEditor'; } - return false; + return 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 926a6d2e7a2..60d058a8fbd 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -4,11 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncUtilService, CONTEXT_SYNC_STATE, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IElectronService } from 'vs/platform/electron/node/electron'; class UserDataSyncServicesContribution implements IWorkbenchContribution { @@ -22,3 +28,24 @@ class UserDataSyncServicesContribution implements IWorkbenchContribution { const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(UserDataSyncServicesContribution, LifecyclePhase.Starting); + +registerAction2(class OpenSyncBackupsFolder extends Action2 { + constructor() { + super({ + id: 'workbench.userData.actions.openSyncBackupsFolder', + 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), + } + }); + } + async run(accessor: ServicesAccessor): Promise { + const syncHome = accessor.get(IEnvironmentService).userDataSyncHome; + const electronService = accessor.get(IElectronService); + const folderStat = await accessor.get(IFileService).resolve(syncHome); + const item = folderStat.children && folderStat.children[0] ? folderStat.children[0].resource : syncHome; + return electronService.showItemInFolder(item.fsPath); + } +}); 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/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 5cf9df18ef8..5da696c6f04 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addClass } from 'vs/base/browser/dom'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -25,6 +26,7 @@ export const enum WebviewMessageChannels { loadResource = 'load-resource', loadLocalhost = 'load-localhost', webviewReady = 'webview-ready', + wheel = 'did-scroll-wheel' } interface IKeydownEvent { @@ -117,6 +119,10 @@ export abstract class BaseWebview extends Disposable { this.handleFocusChange(true); })); + this._register(this.on(WebviewMessageChannels.wheel, (event: IMouseWheelEvent) => { + this._onDidWheel.fire(event); + })); + this._register(this.on(WebviewMessageChannels.didBlur, () => { this.handleFocusChange(false); })); @@ -153,6 +159,9 @@ export abstract class BaseWebview extends Disposable { private readonly _onDidScroll = this._register(new Emitter<{ readonly scrollYPercentage: number; }>()); public readonly onDidScroll = this._onDidScroll.event; + private readonly _onDidWheel = this._register(new Emitter()); + public readonly onDidWheel = this._onDidWheel.event; + private readonly _onDidUpdateState = this._register(new Emitter()); public readonly onDidUpdateState = this._onDidUpdateState.event; @@ -286,4 +295,10 @@ export abstract class BaseWebview extends Disposable { this.element.style.pointerEvents = ''; } } + + public selectAll() { + if (this.element) { + this._send('execCommand', 'selectAll'); + } + } } diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 9848ce0cb70..b7c7a60d8fc 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -3,18 +3,23 @@ * 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; private readonly _pendingMessages = new Set(); private readonly _webview = this._register(new MutableDisposable()); @@ -30,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 @@ -53,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; } @@ -90,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; @@ -98,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(); @@ -106,6 +127,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd this._webviewEvents.add(webview.onDidClickLink(x => { this._onDidClickLink.fire(x); })); this._webviewEvents.add(webview.onMessage(x => { this._onMessage.fire(x); })); this._webviewEvents.add(webview.onMissingCsp(x => { this._onMissingCsp.fire(x); })); + this._webviewEvents.add(webview.onDidWheel(x => { this._onDidWheel.fire(x); })); this._webviewEvents.add(webview.onDidScroll(x => { this._initialScrollProgress = x.scrollYPercentage; @@ -184,8 +206,20 @@ 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()); } + 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() { 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 63c9af47e20..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; @@ -232,23 +233,23 @@ * @param {MouseEvent} event */ const handleAuxClick = - (event) => { - // Prevent middle clicks opening a broken link in the browser - if (!event.view || !event.view.document) { - return; - } - - if (event.button === 1) { - let node = /** @type {any} */ (event.target); - while (node) { - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - event.preventDefault(); - break; - } - node = node.parentNode; + (event) => { + // Prevent middle clicks opening a broken link in the browser + if (!event.view || !event.view.document) { + return; } - } - }; + + if (event.button === 1) { + let node = /** @type {any} */ (event.target); + while (node) { + if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { + event.preventDefault(); + break; + } + node = node.parentNode; + } + } + }; /** * @param {KeyboardEvent} e @@ -267,6 +268,22 @@ }; let isHandlingScroll = false; + + const handleWheel = (event) => { + if (isHandlingScroll) { + return; + } + + host.postMessage('did-scroll-wheel', { + deltaMode: event.deltaMode, + deltaX: event.deltaX, + deltaY: event.deltaY, + deltaZ: event.deltaZ, + detail: event.detail, + type: event.type + }); + }; + const handleInnerScroll = (event) => { if (!event.target || !event.target.body) { return; @@ -308,7 +325,8 @@ // apply default script if (options.allowScripts) { const defaultScript = newDocument.createElement('script'); - defaultScript.textContent = getVsCodeApiScript(data.state); + defaultScript.id = '_vscodeApiScript'; + defaultScript.textContent = getVsCodeApiScript(options.allowMultipleAPIAcquire, data.state); newDocument.head.prepend(defaultScript); } @@ -449,6 +467,10 @@ }, 0); }); + /** + * @param {Document} contentDocument + * @param {Window} contentWindow + */ const onLoad = (contentDocument, contentWindow) => { if (contentDocument && contentDocument.body) { // Workaround for https://github.com/Microsoft/vscode/issues/12865 @@ -471,6 +493,7 @@ } contentWindow.addEventListener('scroll', handleInnerScroll); + contentWindow.addEventListener('wheel', handleWheel); pendingMessages.forEach((data) => { contentWindow.postMessage(data, '*'); @@ -492,10 +515,12 @@ }, 200); newFrame.contentWindow.addEventListener('load', function (e) { + const contentDocument = /** @type {Document} */ (e.target); + if (loadTimeout) { clearTimeout(loadTimeout); loadTimeout = undefined; - onLoad(e.target, this); + onLoad(contentDocument, this); } }); @@ -539,6 +564,13 @@ initData.initialScrollProgress = progress; }); + host.onMessage('execCommand', (_event, data) => { + const target = getActiveFrame(); + if (!target) { + return; + } + target.contentDocument.execCommand(data); + }); trackFocus({ onFocus: () => host.postMessage('did-focus'), diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index c1ce99713e4..b8240e1bc17 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -14,7 +14,7 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; +import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand, SelectAllWebviewEditorCommand } from '../browser/webviewCommands'; import { WebviewEditor } from './webviewEditor'; import { WebviewInput } from './webviewEditorInput'; import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; @@ -50,6 +50,11 @@ registerAction2(class extends WebViewEditorFindPreviousCommand { constructor() { super(webviewActiveContextKeyExpr); } }); +registerAction2(class extends SelectAllWebviewEditorCommand { + constructor() { super(webviewActiveContextKeyExpr); } +}); + + const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction( SyncActionDescriptor.create(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 784bef65db4..dccddca8426 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -12,6 +12,7 @@ import * as nls from 'vs/nls'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; /** * Set when the find widget in a webview is visible. @@ -35,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; } @@ -58,6 +59,7 @@ export interface WebviewOptions { } export interface WebviewContentOptions { + readonly allowMultipleAPIAcquire?: boolean; readonly allowScripts?: boolean; readonly localResourceRoots?: ReadonlyArray; readonly portMapping?: ReadonlyArray; @@ -70,7 +72,6 @@ export interface WebviewExtensionDescription { } export interface Webview extends IDisposable { - html: string; contentOptions: WebviewContentOptions; extension: WebviewExtensionDescription | undefined; @@ -80,6 +81,7 @@ export interface Webview extends IDisposable { readonly onDidFocus: Event; readonly onDidClickLink: Event; readonly onDidScroll: Event<{ scrollYPercentage: number }>; + readonly onDidWheel: Event; readonly onDidUpdateState: Event; readonly onMessage: Event; readonly onMissingCsp: Event; @@ -93,18 +95,28 @@ export interface Webview extends IDisposable { hideFind(): void; runFindAction(previous: boolean): void; + selectAll(): void; + windowDidDragStart(): void; 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 9845fb414d9..9f949d73926 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -7,18 +7,19 @@ import { Action } from 'vs/base/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as nls from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +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'; export class ShowWebViewEditorFindWidgetAction extends Action2 { public static readonly ID = 'editor.action.webvieweditor.showFind'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.showFind', "Show find"); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: ShowWebViewEditorFindWidgetAction.ID, title: ShowWebViewEditorFindWidgetAction.LABEL, @@ -39,7 +40,7 @@ export class HideWebViewEditorFindCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.hideFind'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.hideFind', "Stop find"); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: HideWebViewEditorFindCommand.ID, title: HideWebViewEditorFindCommand.LABEL, @@ -60,7 +61,7 @@ export class WebViewEditorFindNextCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.findNext'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.findNext', 'Find next'); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: WebViewEditorFindNextCommand.ID, title: WebViewEditorFindNextCommand.LABEL, @@ -73,7 +74,7 @@ export class WebViewEditorFindNextCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.find(false); + getActiveWebviewEditor(accessor)?.runFindAction(false); } } @@ -81,7 +82,7 @@ export class WebViewEditorFindPreviousCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.findPrevious'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.findPrevious', 'Find previous'); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: WebViewEditorFindPreviousCommand.ID, title: WebViewEditorFindPreviousCommand.LABEL, @@ -94,9 +95,32 @@ export class WebViewEditorFindPreviousCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.find(true); + getActiveWebviewEditor(accessor)?.runFindAction(true); } } + +export class SelectAllWebviewEditorCommand extends Action2 { + public static readonly ID = 'editor.action.webvieweditor.selectAll'; + public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all'); + + constructor(contextKeyExpr: ContextKeyExpression) { + const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)); + super({ + id: SelectAllWebviewEditorCommand.ID, + title: SelectAllWebviewEditorCommand.LABEL, + keybinding: { + when: precondition, + primary: KeyMod.CtrlCmd | KeyCode.KEY_A, + weight: KeybindingWeight.EditorContrib + } + }); + } + + public run(accessor: ServicesAccessor): void { + getActiveWebviewEditor(accessor)?.selectAll(); + } +} + export class ReloadWebviewAction extends Action { static readonly ID = 'workbench.action.webview.reloadWebviewAction'; static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); @@ -104,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 a0e741c2917..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, Webview, 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,61 +37,36 @@ 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() { - this.withWebview(webview => { - webview.showFind(); - this._findWidgetVisible.set(true); - }); - } - - public hideFind() { - this._findWidgetVisible.reset(); - this.withWebview(webview => webview.hideFind()); - } - - public find(previous: boolean) { - this.withWebview(webview => { - webview.runFindAction(previous); - }); - } - - public reload() { - this.withWebview(webview => webview.reload()); - } - public layout(dimension: DOM.Dimension): void { this._dimension = dimension; - if (this.input && this.input instanceof WebviewInput) { - this.synchronizeWebviewContainerDimensions(this.input.webview, dimension); + if (this.webview) { + this.synchronizeWebviewContainerDimensions(this.webview, dimension); } } @@ -104,27 +75,20 @@ 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(); } }); } - this.withWebview(webview => webview.focus()); - } - - public withWebview(f: (element: Webview) => void): void { - if (this.input && this.input instanceof WebviewInput) { - f(this.input.webview); - } + this.webview?.focus(); } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - if (this.input instanceof WebviewInput) { - const webview = this.input.webview; + if (this.input instanceof WebviewInput && this.webview) { if (visible) { - webview.claim(this); + this.webview.claim(this); } else { - webview.release(this); + this.webview.release(this); } this.claimWebview(this.input); } @@ -132,8 +96,8 @@ export class WebviewEditor extends BaseEditor { } public clearInput() { - if (this.input && this.input instanceof WebviewInput) { - this.input.webview.release(this); + if (this.webview) { + this.webview.release(this); this._webviewVisibleDisposables.clear(); } @@ -145,12 +109,14 @@ export class WebviewEditor extends BaseEditor { return; } - if (this.input && this.input instanceof WebviewInput) { - this.input.webview.release(this); + 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; } @@ -160,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); } @@ -170,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(); @@ -189,15 +152,11 @@ export class WebviewEditor extends BaseEditor { } this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => { - if (this.input instanceof WebviewInput) { - this.input.webview.windowDidDragStart(); - } + this.webview?.windowDidDragStart(); })); const onDragEnd = () => { - if (this.input instanceof WebviewInput) { - this.input.webview.windowDidDragEnd(); - } + this.webview?.windowDidDragEnd(); }; this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_END, onDragEnd)); this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.MOUSE_MOVE, currentEvent => { @@ -210,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 770b2038393..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,17 +17,22 @@ 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: Schemas.webviewPanel, + path: `webview-panel/webview-${this.id}` + }); + } constructor( public readonly id: string, public readonly viewType: string, name: string, - webview: Lazy, + webview: Lazy, @IWebviewService private readonly _webviewService: IWebviewService, ) { super(); @@ -40,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(); @@ -52,13 +53,6 @@ export class WebviewInput extends EditorInput { return WebviewInput.typeId; } - public getResource(): URI { - return URI.from({ - scheme: WebviewPanelResourceScheme, - path: `webview-panel/webview-${this.id}` - }); - } - public getName(): string { return this._name; } @@ -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/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index 538e36abc33..b0471a3d0a1 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -26,8 +26,6 @@ actionRegistry.registerWorkbenchAction( function registerWebViewCommands(editorId: string): void { const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; - registerAction2(class extends webviewCommands.SelectAllWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); - // 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); } }); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index 611622ed72b..83f2a34044a 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -9,10 +9,10 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import * as nls from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +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,29 +34,7 @@ export class OpenWebviewDeveloperToolsAction extends Action { console.error(e); } } - return Promise.resolve(true); - } -} - -export class SelectAllWebviewEditorCommand extends Action2 { - public static readonly ID = 'editor.action.webvieweditor.selectAll'; - public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all'); - - constructor(contextKeyExpr: ContextKeyExpr) { - const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)); - super({ - id: SelectAllWebviewEditorCommand.ID, - title: SelectAllWebviewEditorCommand.LABEL, - keybinding: { - when: precondition, - primary: KeyMod.CtrlCmd | KeyCode.KEY_A, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.selectAll()); + return true; } } @@ -64,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: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: CopyWebviewEditorCommand.ID, title: CopyWebviewEditorCommand.LABEL, @@ -77,7 +55,7 @@ export class CopyWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.copy()); + this.getActiveElectronBasedWebviewDelegate(accessor)?.copy(); } } @@ -85,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: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: PasteWebviewEditorCommand.ID, title: PasteWebviewEditorCommand.LABEL, @@ -98,7 +76,7 @@ export class PasteWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.paste()); + this.getActiveElectronBasedWebviewDelegate(accessor)?.paste(); } } @@ -106,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: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: CutWebviewEditorCommand.ID, title: CutWebviewEditorCommand.LABEL, @@ -119,7 +97,7 @@ export class CutWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.cut()); + this.getActiveElectronBasedWebviewDelegate(accessor)?.cut(); } } @@ -127,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: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: UndoWebviewEditorCommand.ID, title: UndoWebviewEditorCommand.LABEL, @@ -139,8 +117,8 @@ export class UndoWebviewEditorCommand extends Action2 { }); } - public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.undo()); + public run(accessor: ServicesAccessor): void { + this.getActiveElectronBasedWebviewDelegate(accessor)?.undo(); } } @@ -148,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: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression, readonly getActiveElectronBasedWebviewDelegate: (accessor: ServicesAccessor) => ElectronWebviewBasedWebview | undefined = getActiveElectronBasedWebview) { super({ id: RedoWebviewEditorCommand.ID, title: RedoWebviewEditorCommand.LABEL, @@ -162,23 +140,25 @@ export class RedoWebviewEditorCommand extends Action2 { }); } - public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.redo()); + public run(accessor: ServicesAccessor): void { + this.getActiveElectronBasedWebviewDelegate(accessor)?.redo(); } } -function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: ElectronWebviewBasedWebview) => void): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.withWebview(webview => { - if (webview instanceof ElectronWebviewBasedWebview) { - f(webview); - } else if ((webview as WebviewEditorOverlay).getInnerWebview) { - const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview(); - if (innerWebview instanceof ElectronWebviewBasedWebview) { - f(innerWebview); - } - } - }); +function getActiveElectronBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { + const webview = getActiveWebviewEditor(accessor); + if (!webview) { + return undefined; } + + if (webview instanceof ElectronWebviewBasedWebview) { + return webview; + } else if ((webview as WebviewOverlay).getInnerWebview) { + const innerWebview = (webview as WebviewOverlay).getInnerWebview(); + if (innerWebview instanceof ElectronWebviewBasedWebview) { + return innerWebview; + } + } + + return undefined; } 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/viewsWelcome.contribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcome.contribution.ts new file mode 100644 index 00000000000..9153e3c1a4d --- /dev/null +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcome.contribution.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { ViewsWelcomeContribution } from 'vs/workbench/contrib/welcome/common/viewsWelcomeContribution'; +import { ViewsWelcomeExtensionPoint, viewsWelcomeExtensionPointDescriptor } from 'vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +const extensionPoint = ExtensionsRegistry.registerExtensionPoint(viewsWelcomeExtensionPointDescriptor); + +class WorkbenchConfigurationContribution { + constructor( + @IInstantiationService instantiationService: IInstantiationService, + ) { + instantiationService.createInstance(ViewsWelcomeContribution, extensionPoint); + } +} + +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(WorkbenchConfigurationContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts new file mode 100644 index 00000000000..3ee82a7b2c2 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeContribution.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * 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 } 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, ViewIdentifierMap } from './viewsWelcomeExtensionPoint'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ViewContainerExtensions, IViewsRegistry, ViewContentPriority } from 'vs/workbench/common/views'; + +const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); + +export class ViewsWelcomeContribution extends Disposable implements IWorkbenchContribution { + + private viewWelcomeContents = new Map(); + + constructor(extensionPoint: IExtensionPoint) { + super(); + + extensionPoint.setHandler((_, { added, removed }) => { + for (const contribution of removed) { + for (const welcome of contribution.value) { + const disposable = this.viewWelcomeContents.get(welcome); + + if (disposable) { + disposable.dispose(); + } + } + } + + for (const contribution of added) { + for (const welcome of contribution.value) { + const id = ViewIdentifierMap[welcome.view] ?? welcome.view; + const disposable = viewsRegistry.registerViewWelcomeContent(id, { + content: welcome.contents, + when: ContextKeyExpr.deserialize(welcome.when), + priority: contribution.description.isBuiltin ? ViewContentPriority.Low : ViewContentPriority.Lowest + }); + + this.viewWelcomeContents.set(welcome, disposable); + } + } + }); + } +} diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts new file mode 100644 index 00000000000..68d5c4a6e64 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; + +export enum ViewsWelcomeExtensionPointFields { + view = 'view', + contents = 'contents', + when = 'when', +} + +export interface ViewWelcome { + readonly [ViewsWelcomeExtensionPointFields.view]: string; + readonly [ViewsWelcomeExtensionPointFields.contents]: string; + readonly [ViewsWelcomeExtensionPointFields.when]: string; +} + +export type ViewsWelcomeExtensionPoint = ViewWelcome[]; + +export const ViewIdentifierMap: { [key: string]: string } = { + 'explorer': 'workbench.explorer.emptyView', + 'debug': 'workbench.debug.welcome', + 'scm': 'workbench.scm', +}; + +const viewsWelcomeExtensionPointSchema = Object.freeze({ + type: 'array', + description: nls.localize('contributes.viewsWelcome', "Contributed views welcome content. Welcome content will be rendered in views whenever they have no meaningful content to display, ie. the File Explorer when no folder is open. Such content is useful as in-product documentation to drive users to use certain features before they are available. A good example would be a `Clone Repository` button in the File Explorer welcome view."), + items: { + type: 'object', + description: nls.localize('contributes.viewsWelcome.view', "Contributed welcome content for a specific view."), + required: [ + ViewsWelcomeExtensionPointFields.view, + ViewsWelcomeExtensionPointFields.contents + ], + properties: { + [ViewsWelcomeExtensionPointFields.view]: { + anyOf: [ + { + type: 'string', + description: nls.localize('contributes.viewsWelcome.view.view', "Target view identifier for this welcome content.") + }, + { + type: 'string', + description: nls.localize('contributes.viewsWelcome.view.view', "Target view identifier for this welcome content."), + enum: Object.keys(ViewIdentifierMap) + } + ] + }, + [ViewsWelcomeExtensionPointFields.contents]: { + type: 'string', + description: nls.localize('contributes.viewsWelcome.view.contents', "Welcome content to be displayed. The format of the contents is a subset of Markdown, with support for links only."), + }, + [ViewsWelcomeExtensionPointFields.when]: { + type: 'string', + description: nls.localize('contributes.viewsWelcome.view.when', "Condition when the welcome content should be displayed."), + }, + } + } +}); + +export const viewsWelcomeExtensionPointDescriptor = { + extensionPoint: 'viewsWelcome', + jsonSchema: viewsWelcomeExtensionPointSchema +}; diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index 81c711a0380..447abf02489 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -7,9 +7,9 @@ import 'vs/css!./welcomeOverlay'; import * as dom from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickopen/browser/commandsHandler'; +import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; @@ -156,7 +156,7 @@ class WelcomeOverlay extends Disposable { private _overlay!: HTMLElement; constructor( - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @ILayoutService private readonly layoutService: ILayoutService, @IEditorService private readonly editorService: IEditorService, @ICommandService private readonly commandService: ICommandService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -168,8 +168,8 @@ class WelcomeOverlay extends Disposable { } private create(): void { - const offset = this.layoutService.getTitleBarOffset(); - this._overlay = dom.append(this.layoutService.getWorkbenchElement(), $('.welcomeOverlay')); + const offset = this.layoutService.offset?.top ?? 0; + this._overlay = dom.append(this.layoutService.container, $('.welcomeOverlay')); this._overlay.style.top = `${offset}px`; this._overlay.style.height = `calc(100% - ${offset}px)`; this._overlay.style.display = 'none'; diff --git a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts index ab2af19a26a..30f321498e8 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; export default () => `
-
+

${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/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 5b610fe6152..81e81e8ff0b 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -24,7 +24,7 @@ import { Schemas } from 'vs/base/common/network'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle'; import { splitName } from 'vs/base/common/labels'; @@ -260,7 +260,7 @@ class WelcomePage extends Disposable { @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionTipsService private readonly tipsService: IExtensionTipsService, + @IExtensionRecommendationsService private readonly tipsService: IExtensionRecommendationsService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @ILifecycleService lifecycleService: ILifecycleService, @ITelemetryService private readonly telemetryService: ITelemetryService, 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/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts index c8c3d2d6cb8..ace7c0bc627 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts @@ -53,17 +53,15 @@ export class WalkThroughInput extends EditorInput { private maxTopScroll = 0; private maxBottomScroll = 0; + get resource() { return this.options.resource; } + constructor( - private options: WalkThroughInputOptions, + private readonly options: WalkThroughInputOptions, @ITextModelService private readonly textModelResolverService: ITextModelService ) { super(); } - getResource(): URI { - return this.options.resource; - } - getTypeId(): string { return this.options.typeId; } @@ -99,7 +97,7 @@ export class WalkThroughInput extends EditorInput { if (!this.promise) { this.promise = this.textModelResolverService.createModelReference(this.options.resource) .then(ref => { - if (strings.endsWith(this.getResource().path, '.html')) { + if (strings.endsWith(this.resource.path, '.html')) { return new WalkThroughModel(ref, []); } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 2be3aee8874..7cc9d291293 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -280,7 +280,7 @@ export class WalkThroughPart extends BaseEditor { } const content = model.main.textEditorModel.getValue(EndOfLinePreference.LF); - if (!strings.endsWith(input.getResource().path, '.md')) { + if (!strings.endsWith(input.resource.path, '.md')) { this.content.innerHTML = content; this.updateSizeClasses(); this.decorateContent(); @@ -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/electron-browser/actions/media/actions.css b/src/vs/workbench/electron-browser/actions/media/actions.css new file mode 100644 index 00000000000..553c68adf9f --- /dev/null +++ b/src/vs/workbench/electron-browser/actions/media/actions.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. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .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..19e19c5f6b6 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,20 +154,26 @@ 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 quickInputService: IQuickInputService, - private keybindingService: IKeybindingService, - private modelService: IModelService, - private modeService: IModeService, - private electronService: IElectronService + private readonly environmentService: INativeWorkbenchEnvironmentService, + private readonly quickInputService: IQuickInputService, + private readonly keybindingService: IKeybindingService, + private readonly modelService: IModelService, + private readonly modeService: IModeService, + private readonly electronService: IElectronService ) { super(id, label); } @@ -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 f1d8f9af253..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') }); }); } @@ -341,6 +341,10 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; 'disable-color-correct-rendering': { type: 'boolean', description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') + }, + 'force-color-profile': { + type: 'string', + markdownDescription: nls.localize('argv.forceColorProfile', 'Allows to override the color profile to use. If you experience colors appear badly, try to set this to `srgb` and restart.') } } }; diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index e9da83b049d..84c22595e12 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,17 +48,16 @@ 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; + private readonly environmentService = new NativeWorkbenchEnvironmentService(this.configuration, this.configuration.execPath); - constructor(private configuration: IWindowConfiguration) { + constructor(private configuration: INativeWindowConfiguration) { super(); - this.environmentService = new NativeWorkbenchEnvironmentService(configuration, configuration.execPath, configuration.windowId); - this.init(); } @@ -127,7 +123,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 +176,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 }); @@ -201,7 +192,7 @@ class DesktopMain extends Disposable { const signService = new SignService(); serviceCollection.set(ISignService, signService); - const remoteAgentService = this._register(new RemoteAgentService(this.environmentService.configuration, this.environmentService, remoteAuthorityResolverService, signService, logService)); + const remoteAgentService = this._register(new RemoteAgentService(this.environmentService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); // Files @@ -220,7 +211,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 +240,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 +250,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 +270,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 +337,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 fdac09565c0..26c12fcf5c9 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -6,20 +6,21 @@ 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 { IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +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, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IRunActionInWindowRequest, IRunKeybindingInWindowRequest, IAddFoldersRequest, INativeOpenFileRequest } from 'vs/platform/windows/node/window'; 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 +47,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 +59,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()); @@ -76,7 +78,7 @@ export class ElectronWindow extends Disposable { private readonly addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); private pendingFoldersToAdd: URI[] = []; - private readonly closeEmptyWindowScheduler: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50)); + private readonly closeEmptyWindowScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50)); private isDocumentedEdited = false; @@ -94,7 +96,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 +104,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 +182,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 +252,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 +270,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); @@ -289,10 +293,9 @@ export class ElectronWindow extends Disposable { private updateDocumentEdited(isDirty = this.workingCopyService.hasDirty): void { if ((!this.isDocumentedEdited && isDirty) || (this.isDocumentedEdited && !isDirty)) { - const hasDirtyFiles = this.workingCopyService.hasDirty; - this.isDocumentedEdited = hasDirtyFiles; + this.isDocumentedEdited = isDirty; - this.electronService.setDocumentEdited(hasDirtyFiles); + this.electronService.setDocumentEdited(isDirty); } } @@ -305,8 +308,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(); @@ -315,8 +318,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(); } } @@ -396,7 +399,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 })); @@ -423,8 +426,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); } } @@ -537,31 +540,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,8 +595,8 @@ export class ElectronWindow extends Disposable { this.workspaceEditingService.addFolders(foldersToAdd); } - private async onOpenFiles(request: IOpenFileRequest): Promise { - const inputs: IResourceEditor[] = []; + private async onOpenFiles(request: INativeOpenFileRequest): Promise { + const inputs: IResourceEditorInputType[] = []; const diffMode = !!(request.filesToDiff && (request.filesToDiff.length === 2)); if (!diffMode && request.filesToOpenOrCreate) { @@ -614,6 +622,8 @@ export class ElectronWindow extends Disposable { } private trackClosedWaitFiles(waitMarkerFile: URI, resourcesToWaitFor: URI[]): IDisposable { + let remainingResourcesToWaitFor = resourcesToWaitFor.slice(0); + // In wait mode, listen to changes to the editors and wait until the files // are closed that the user wants to wait for. When this happens we delete // the wait marker file to signal to the outside that editing is done. @@ -623,7 +633,7 @@ export class ElectronWindow extends Disposable { // Remove from resources to wait for based on the // resources from editors that got closed - resourcesToWaitFor = resourcesToWaitFor.filter(resourceToWaitFor => { + remainingResourcesToWaitFor = remainingResourcesToWaitFor.filter(resourceToWaitFor => { if (isEqual(resourceToWaitFor, masterResource) || isEqual(resourceToWaitFor, detailsResource)) { return false; // remove - the closing editor matches this resource } @@ -631,7 +641,7 @@ export class ElectronWindow extends Disposable { return true; // keep - not yet closed }); - if (resourcesToWaitFor.length === 0) { + if (remainingResourcesToWaitFor.length === 0) { // If auto save is configured with the default delay (1s) it is possible // to close the editor while the save still continues in the background. As such // we have to also check if the files to wait for are dirty and if so wait @@ -666,7 +676,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 @@ -696,11 +706,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, @@ -749,7 +758,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 84% rename from src/vs/workbench/services/accessibility/node/accessibilityService.ts rename to src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts index a2911ba04a5..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 { @@ -78,11 +79,13 @@ class LinuxAccessibilityContribution implements IWorkbenchContribution { @IAccessibilityService accessibilityService: AccessibilityService, @IEnvironmentService environmentService: IEnvironmentService ) { - accessibilityService.onDidChangeScreenReaderOptimized(async () => { + const forceRendererAccessibility = () => { if (accessibilityService.isScreenReaderOptimized()) { - await jsonEditingService.write(environmentService.argvResource, [{ key: 'force-renderer-accessibility', value: true }], true); + jsonEditingService.write(environmentService.argvResource, [{ key: 'force-renderer-accessibility', value: true }], true); } - }); + }; + forceRendererAccessibility(); + accessibilityService.onDidChangeScreenReaderOptimized(forceRendererAccessibility); } } diff --git a/src/vs/workbench/services/activity/common/activity.ts b/src/vs/workbench/services/activity/common/activity.ts index 628590befc4..d8bdaca851b 100644 --- a/src/vs/workbench/services/activity/common/activity.ts +++ b/src/vs/workbench/services/activity/common/activity.ts @@ -6,14 +6,25 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +export const IActivityService = createDecorator('activityService'); + +export interface IActivityService { + + _serviceBrand: undefined; + + /** + * Show activity in the panel for the given panel or in the activitybar for the given viewlet or global action. + */ + showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; +} + export interface IBadge { getDescription(): string; } -export class BaseBadge implements IBadge { - descriptorFn: (args: any) => string; +class BaseBadge implements IBadge { - constructor(descriptorFn: (args: any) => string) { + constructor(public readonly descriptorFn: (arg: any) => string) { this.descriptorFn = descriptorFn; } @@ -23,9 +34,8 @@ export class BaseBadge implements IBadge { } export class NumberBadge extends BaseBadge { - number: number; - constructor(number: number, descriptorFn: (args: any) => string) { + constructor(public readonly number: number, descriptorFn: (num: number) => string) { super(descriptorFn); this.number = number; @@ -37,31 +47,17 @@ export class NumberBadge extends BaseBadge { } export class TextBadge extends BaseBadge { - text: string; - constructor(text: string, descriptorFn: (args: any) => string) { + constructor(public readonly text: string, descriptorFn: () => string) { super(descriptorFn); - - this.text = text; } } export class IconBadge extends BaseBadge { - constructor(descriptorFn: (args: any) => string) { + constructor(descriptorFn: () => string) { super(descriptorFn); } } export class ProgressBadge extends BaseBadge { } - -export const IActivityService = createDecorator('activityService'); - -export interface IActivityService { - _serviceBrand: undefined; - - /** - * Show activity in the panel for the given panel or in the activitybar for the given viewlet or global action. - */ - showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; -} 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/userDataSync/electron-browser/userDataAuthTokenService.ts b/src/vs/workbench/services/authentication/electron-browser/authenticationTokenService.ts similarity index 69% rename from src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts rename to src/vs/workbench/services/authentication/electron-browser/authenticationTokenService.ts index 998630e1ef9..d20106f6579 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts +++ b/src/vs/workbench/services/authentication/electron-browser/authenticationTokenService.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; +import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; -export class UserDataAuthTokenService extends Disposable implements IUserDataAuthTokenService { +export class AuthenticationTokenService extends Disposable implements IAuthenticationTokenService { _serviceBrand: undefined; @@ -18,11 +18,15 @@ export class UserDataAuthTokenService extends Disposable implements IUserDataAut private _onDidChangeToken: Emitter = this._register(new Emitter()); readonly onDidChangeToken: Event = this._onDidChangeToken.event; + private _onTokenFailed: Emitter = this._register(new Emitter()); + readonly onTokenFailed: Event = this._onTokenFailed.event; + constructor( @ISharedProcessService sharedProcessService: ISharedProcessService, ) { super(); this.channel = sharedProcessService.getChannel('authToken'); + this._register(this.channel.listen('onTokenFailed')(_ => this.sendTokenFailed())); } getToken(): Promise { @@ -32,6 +36,10 @@ export class UserDataAuthTokenService extends Disposable implements IUserDataAut setToken(token: string | undefined): Promise { return this.channel.call('setToken', token); } + + sendTokenFailed(): void { + this._onTokenFailed.fire(); + } } -registerSingleton(IUserDataAuthTokenService, UserDataAuthTokenService); +registerSingleton(IAuthenticationTokenService, AuthenticationTokenService); 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/electron-browser/backup.ts b/src/vs/workbench/services/backup/electron-browser/backup.ts index 6d6bfd34246..0057b918a0b 100644 --- a/src/vs/workbench/services/backup/electron-browser/backup.ts +++ b/src/vs/workbench/services/backup/electron-browser/backup.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { joinPath, relativePath } from 'vs/base/common/resources'; -export function toBackupWorkspaceResource(backupWorkspacePath: string, environmentService: IEnvironmentService): URI { +export function toBackupWorkspaceResource(backupWorkspacePath: string, environmentService: INativeEnvironmentService): URI { return joinPath(environmentService.userRoamingDataHome, relativePath(URI.file(environmentService.userDataPath), URI.file(backupWorkspacePath))!); } 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 77e239607a4..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 @@ -12,7 +12,8 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { BackupFilesModel } from 'vs/workbench/services/backup/common/backupFileService'; -import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; @@ -42,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); } } @@ -61,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 = []; @@ -98,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', () => { @@ -205,7 +216,7 @@ suite('BackupFileService', () => { }); test('text file (ITextSnapshot)', async () => { - const model = TextModel.createFromString('test'); + const model = createTextModel('test'); await service.backup(fooFile, model.createSnapshot()); assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); @@ -217,7 +228,7 @@ suite('BackupFileService', () => { }); test('untitled file (ITextSnapshot)', async () => { - const model = TextModel.createFromString('test'); + const model = createTextModel('test'); await service.backup(untitledFile, model.createSnapshot()); assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); @@ -229,7 +240,7 @@ suite('BackupFileService', () => { test('text file (large file, ITextSnapshot)', async () => { const largeString = (new Array(10 * 1024)).join('Large String\n'); - const model = TextModel.createFromString(largeString); + const model = createTextModel(largeString); await service.backup(fooFile, model.createSnapshot()); assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); @@ -242,7 +253,7 @@ suite('BackupFileService', () => { test('untitled file (large file, ITextSnapshot)', async () => { const largeString = (new Array(10 * 1024)).join('Large String\n'); - const model = TextModel.createFromString(largeString); + const model = createTextModel(largeString); await service.backup(untitledFile, model.createSnapshot()); assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); @@ -275,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)); @@ -472,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); @@ -484,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 199f78af912..23cc7b80b96 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -10,6 +10,7 @@ import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditOptions, IBulkEditResult, IBulkEditService, IBulkEditPreviewHandler } from 'vs/editor/browser/services/bulkEditService'; 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 { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { WorkspaceFileEdit, WorkspaceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -18,27 +19,29 @@ import { localize } from 'vs/nls'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgress, IProgressStep, emptyProgress } from 'vs/platform/progress/common/progress'; +import { IProgress, IProgressStep, Progress } from 'vs/platform/progress/common/progress'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; 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 { SingleModelEditStackElement, MultiModelEditStackElement } from 'vs/editor/common/model/editStack'; type ValidationResult = { canApply: true } | { canApply: false, reason: URI }; class ModelEditTask implements IDisposable { - private readonly _model: ITextModel; + public readonly model: ITextModel; protected _edits: IIdentifiedSingleEditOperation[]; private _expectedModelVersionId: number | undefined; protected _newEol: EndOfLineSequence | undefined; constructor(private readonly _modelReference: IReference) { - this._model = this._modelReference.object.textEditorModel; + this.model = this._modelReference.object.textEditorModel; this._edits = []; } @@ -66,7 +69,7 @@ class ModelEditTask implements IDisposable { // create edit operation let range: Range; if (!edit.range) { - range = this._model.getFullModelRange(); + range = this.model.getFullModelRange(); } else { range = Range.lift(edit.range); } @@ -74,23 +77,23 @@ class ModelEditTask implements IDisposable { } validate(): ValidationResult { - if (typeof this._expectedModelVersionId === 'undefined' || this._model.getVersionId() === this._expectedModelVersionId) { + if (typeof this._expectedModelVersionId === 'undefined' || this.model.getVersionId() === this._expectedModelVersionId) { return { canApply: true }; } - return { canApply: false, reason: this._model.uri }; + return { canApply: false, reason: this.model.uri }; + } + + getBeforeCursorState(): Selection[] | null { + return null; } apply(): void { if (this._edits.length > 0) { this._edits = mergeSort(this._edits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - this._model.pushStackElement(); - this._model.pushEditOperations([], this._edits, () => []); - this._model.pushStackElement(); + this.model.pushEditOperations(null, this._edits, () => null); } if (this._newEol !== undefined) { - this._model.pushStackElement(); - this._model.pushEOL(this._newEol); - this._model.pushStackElement(); + this.model.pushEOL(this._newEol); } } } @@ -104,18 +107,18 @@ class EditorEditTask extends ModelEditTask { this._editor = editor; } + getBeforeCursorState(): Selection[] | null { + return this._editor.getSelections(); + } + apply(): void { if (this._edits.length > 0) { this._edits = mergeSort(this._edits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - this._editor.pushUndoStop(); this._editor.executeEdits('', this._edits); - this._editor.pushUndoStop(); } if (this._newEol !== undefined) { if (this._editor.hasModel()) { - this._editor.pushUndoStop(); this._editor.getModel().pushEOL(this._newEol); - this._editor.pushUndoStop(); } } } @@ -127,11 +130,13 @@ class BulkEditModel implements IDisposable { private _tasks: ModelEditTask[] | undefined; constructor( + private readonly _label: string | undefined, private readonly _editor: ICodeEditor | undefined, private readonly _progress: IProgress, edits: WorkspaceTextEdit[], @IEditorWorkerService private readonly _editorWorker: IEditorWorkerService, @ITextModelService private readonly _textModelResolverService: ITextModelService, + @IUndoRedoService private readonly _undoRedoService: IUndoRedoService ) { edits.forEach(this._addEdit, this); } @@ -214,10 +219,31 @@ class BulkEditModel implements IDisposable { } apply(): void { - for (const task of this._tasks!) { + const tasks = this._tasks!; + + if (tasks.length === 1) { + // This edit touches a single model => keep things simple + for (const task of tasks) { + task.model.pushStackElement(); + task.apply(); + task.model.pushStackElement(); + this._progress.report(undefined); + } + return; + } + + const multiModelEditStackElement = new MultiModelEditStackElement( + this._label || localize('workspaceEdit', "Workspace Edit"), + tasks.map(t => new SingleModelEditStackElement(t.model, t.getBeforeCursorState())) + ); + this._undoRedoService.pushElement(multiModelEditStackElement); + + for (const task of tasks) { task.apply(); this._progress.report(undefined); } + + multiModelEditStackElement.close(); } } @@ -225,11 +251,13 @@ type Edit = WorkspaceFileEdit | WorkspaceTextEdit; class BulkEdit { + private readonly _label: string | undefined; private readonly _edits: Edit[] = []; private readonly _editor: ICodeEditor | undefined; private readonly _progress: IProgress; constructor( + label: string | undefined, editor: ICodeEditor | undefined, progress: IProgress | undefined, edits: Edit[], @@ -237,10 +265,12 @@ class BulkEdit { @ILogService private readonly _logService: ILogService, @IFileService private readonly _fileService: IFileService, @ITextFileService private readonly _textFileService: ITextFileService, + @IWorkingCopyFileService private readonly _workingCopyFileService: IWorkingCopyFileService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { + this._label = label; this._editor = editor; - this._progress = progress || emptyProgress; + this._progress = progress || Progress.None; this._edits = edits; } @@ -309,7 +339,7 @@ class BulkEdit { if (options.overwrite === undefined && options.ignoreIfExists && await this._fileService.exists(edit.newUri)) { continue; // not overwriting, but ignoring, and the target file exists } - await this._textFileService.move(edit.oldUri, edit.newUri, options.overwrite); + await this._workingCopyFileService.move(edit.oldUri, edit.newUri, options.overwrite); } else if (!edit.newUri && edit.oldUri) { // delete file @@ -318,7 +348,7 @@ class BulkEdit { if (useTrash && !(this._fileService.hasCapability(edit.oldUri, FileSystemProviderCapabilities.Trash))) { useTrash = false; // not supported by provider } - await this._textFileService.delete(edit.oldUri, { useTrash, recursive: options.recursive }); + await this._workingCopyFileService.delete(edit.oldUri, { useTrash, recursive: options.recursive }); } else if (!options.ignoreIfNotExists) { throw new Error(`${edit.oldUri} does not exist and can not be deleted`); } @@ -335,7 +365,7 @@ class BulkEdit { private async _performTextEdits(edits: WorkspaceTextEdit[], progress: IProgress): Promise { this._logService.debug('_performTextEdits', JSON.stringify(edits)); - const model = this._instaService.createInstance(BulkEditModel, this._editor, progress, edits); + const model = this._instaService.createInstance(BulkEditModel, this._label, this._editor, progress, edits); await model.prepare(); @@ -403,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; } @@ -413,7 +443,7 @@ export class BulkEditService implements IBulkEditService { // If the code editor is readonly still allow bulk edits to be applied #68549 codeEditor = undefined; } - const bulkEdit = this._instaService.createInstance(BulkEdit, codeEditor, options?.progress, edits); + const bulkEdit = this._instaService.createInstance(BulkEdit, options?.quotableLabel || options?.label, codeEditor, options?.progress, edits); return bulkEdit.perform().then(() => { return { ariaSummary: bulkEdit.ariaMessage() }; }).catch(err => { diff --git a/src/vs/workbench/services/bulkEdit/browser/conflicts.ts b/src/vs/workbench/services/bulkEdit/browser/conflicts.ts index 66532130949..99340e7740b 100644 --- a/src/vs/workbench/services/bulkEdit/browser/conflicts.ts +++ b/src/vs/workbench/services/bulkEdit/browser/conflicts.ts @@ -50,7 +50,7 @@ export class ConflictDetector { } // listen to file changes - this._disposables.add(fileService.onFileChanges(e => { + this._disposables.add(fileService.onDidFilesChange(e => { for (let change of e.changes) { if (modelService.getModel(change.resource)) { diff --git a/src/vs/workbench/services/clipboard/browser/clipboardService.ts b/src/vs/workbench/services/clipboard/browser/clipboardService.ts index 85a5d9db60c..cd3e76e7c63 100644 --- a/src/vs/workbench/services/clipboard/browser/clipboardService.ts +++ b/src/vs/workbench/services/clipboard/browser/clipboardService.ts @@ -5,73 +5,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { URI } from 'vs/base/common/uri'; - -export class BrowserClipboardService implements IClipboardService { - - _serviceBrand: undefined; - - private _internalResourcesClipboard: URI[] | undefined; - - async writeText(text: string, type?: string): Promise { - if (type) { - return; // TODO@sbatten - } - - if (navigator.clipboard && navigator.clipboard.writeText) { - return navigator.clipboard.writeText(text); - } else { - const activeElement = document.activeElement; - const newTextarea = document.createElement('textarea'); - newTextarea.className = 'clipboard-copy'; - newTextarea.style.visibility = 'false'; - newTextarea.style.height = '1px'; - newTextarea.style.width = '1px'; - newTextarea.setAttribute('aria-hidden', 'true'); - newTextarea.style.position = 'absolute'; - newTextarea.style.top = '-1000'; - newTextarea.style.left = '-1000'; - document.body.appendChild(newTextarea); - newTextarea.value = text; - newTextarea.focus(); - newTextarea.select(); - document.execCommand('copy'); - activeElement.focus(); - document.body.removeChild(newTextarea); - } - return; - } - - async readText(type?: string): Promise { - if (type) { - return ''; // TODO@sbatten - } - - return navigator.clipboard.readText(); - } - - readTextSync(): string | undefined { - return undefined; - } - - readFindText(): string { - // @ts-ignore - return undefined; - } - - writeFindText(text: string): void { } - - writeResources(resources: URI[]): void { - this._internalResourcesClipboard = resources; - } - - readResources(): URI[] { - return this._internalResourcesClipboard || []; - } - - hasResources(): boolean { - return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0; - } -} +import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; registerSingleton(IClipboardService, BrowserClipboardService, true); 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 ba9c729a3e4..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,56 +73,67 @@ 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(); this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); - this._register(this.fileService.onFileChanges((e) => this.handleFileEvents(e))); + this._register(this.fileService.onDidFilesChange((e) => this.handleFileEvents(e))); } 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 { @@ -268,7 +261,7 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable { super(); this.parser = new ConfigurationModelParser(this.configurationResource.toString(), this.scopes); - this._register(fileService.onFileChanges(e => this.handleFileEvents(e))); + this._register(fileService.onDidFilesChange(e => this.handleFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); this._register(toDisposable(() => { this.stopWatchingResource(); @@ -523,7 +516,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWork this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(''); this.workspaceSettings = new ConfigurationModel(); - this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); + this._register(fileService.onDidFilesChange(e => this.handleWorkspaceFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); this.workspaceConfigWatcher = this._register(this.watchWorkspaceConfigurationFile()); } @@ -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..652eba904de 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,24 +546,36 @@ 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); - const changes = this.compareFolders(this.workspace.folders, configuredFolders); - if (changes.added.length || changes.removed.length || changes.changed.length) { - this.workspace.folders = configuredFolders; - return this.onFoldersChanged() - .then(change => { - this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE_FOLDER); - this._onDidChangeWorkspaceFolders.fire(changes); - }); - } else { - this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE); + let newFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration); + const { added, removed, changed } = this.compareFolders(this.workspace.folders, newFolders); + + /* If changed validate new folders */ + if (added.length || removed.length || changed.length) { + newFolders = await this.toValidWorkspaceFolders(newFolders); } + /* Otherwise use existing */ + else { + newFolders = this.workspace.folders; + } + + await this.updateWorkspaceConfiguration(newFolders, this.workspaceConfiguration.getConfiguration()); + } + } + + private async updateWorkspaceConfiguration(workspaceFolders: WorkspaceFolder[], configuration: ConfigurationModel): Promise { + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const change = this._configuration.compareAndUpdateWorkspaceConfiguration(configuration); + const changes = this.compareFolders(this.workspace.folders, workspaceFolders); + if (changes.added.length || changes.removed.length || changes.changed.length) { + this.workspace.folders = workspaceFolders; + const change = await this.onFoldersChanged(); + this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE_FOLDER); + this._onDidChangeWorkspaceFolders.fire(changes); + } else { + this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE); } - return Promise.resolve(undefined); } private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): Promise { @@ -600,6 +627,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) { + await this.updateWorkspaceConfiguration(validWorkspaceFolders, this.workspaceConfiguration.getConfiguration()); + } + } + + 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/node/configurationCache.ts b/src/vs/workbench/services/configuration/node/configurationCache.ts index 432c8ada983..03f50c76630 100644 --- a/src/vs/workbench/services/configuration/node/configurationCache.ts +++ b/src/vs/workbench/services/configuration/node/configurationCache.ts @@ -4,16 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as pfs from 'vs/base/node/pfs'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join } from 'vs/base/common/path'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration'; -import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService'; export class ConfigurationCache implements IConfigurationCache { private readonly cachedConfigurations: Map = new Map(); - constructor(private readonly environmentService: IWorkbenchEnvironmentService) { + constructor(private readonly environmentService: INativeEnvironmentService) { } read(key: ConfigurationKey): Promise { @@ -48,7 +47,7 @@ class CachedConfiguration { constructor( { type, key }: ConfigurationKey, - environmentService: IEnvironmentService + environmentService: INativeEnvironmentService ) { this.cachedConfigurationFolderPath = join(environmentService.userDataPath, 'CachedConfigurations', type, key); this.cachedConfigurationFilePath = join(this.cachedConfigurationFolderPath, type === 'workspaces' ? 'workspace.json' : 'configuration.json'); 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..92b28f0cc2a 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,11 +45,10 @@ 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; } - } suite('ConfigurationEditingService', () => { @@ -107,7 +106,7 @@ suite('ConfigurationEditingService', () => { instantiationService = workbenchInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(workspaceDir)); instantiationService.stub(IEnvironmentService, environmentService); - const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); + const remoteAgentService = instantiationService.createInstance(RemoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); 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..dcd1aff9aa6 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; } @@ -109,7 +111,7 @@ suite('WorkspaceContextService - Folder', () => { const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, new DiskFileSystemProvider(new NullLogService()), environmentService)); - workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService(TestWindowConfiguration, environmentService, new RemoteAuthorityResolverService(), new SignService(undefined), new NullLogService())); + workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService(environmentService, new RemoteAuthorityResolverService(), new SignService(undefined), new NullLogService())); return (workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir))); }); }); @@ -169,7 +171,7 @@ suite('WorkspaceContextService - Workspace', () => { instantiationService = workbenchInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(parentDir)); - const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); + const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); @@ -229,7 +231,7 @@ suite('WorkspaceContextService - Workspace Editing', () => { instantiationService = workbenchInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(parentDir)); - const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); + const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); @@ -490,7 +492,7 @@ suite('WorkspaceService - Initialization', () => { const instantiationService = workbenchInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(parentDir)); - const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); + const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); @@ -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({ @@ -765,17 +769,20 @@ suite('WorkspaceConfigurationService - Folder', () => { const instantiationService = workbenchInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(parentDir)); - const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); + 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', () => { @@ -1155,7 +1194,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { const instantiationService = workbenchInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(parentDir)); - const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); + const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index f5c0828bf5f..42b3431dc41 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'; @@ -28,9 +28,9 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR static readonly INPUT_OR_COMMAND_VARIABLES_PATTERN = /\${((input|command):(.*?))}/g; constructor( + context: { getExecPath: () => string | undefined }, envVariables: IProcessEnvironment, editorService: IEditorService, - environmentService: IWorkbenchEnvironmentService, private readonly configurationService: IConfigurationService, private readonly commandService: ICommandService, private readonly workspaceContextService: IWorkspaceContextService, @@ -48,7 +48,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR return configurationService.getValue(suffix, folderUri ? { resource: folderUri } : {}); }, getExecPath: (): string | undefined => { - return environmentService['execPath']; + return context.getExecPath(); }, getFilePath: (): string | undefined => { let activeEditor = editorService.activeEditor; @@ -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; + } } } @@ -259,6 +268,9 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR if (info.default) { inputOptions.value = info.default; } + if (info.password) { + inputOptions.password = info.password; + } return this.quickInputService.input(inputOptions).then(resolvedInput => { return resolvedInput; }); @@ -337,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({ getExecPath: () => undefined }, Object.create(null), editorService, 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 3c74d925a11..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, @@ -49,6 +50,7 @@ export interface PromptStringInputInfo { type: 'promptString'; description: string; default?: string; + password?: boolean; } export interface PickStringInputInfo { diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts index bc2ba69de5c..2fceba9e4ac 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts @@ -44,6 +44,10 @@ export const inputsSchema: IJSONSchema = { type: 'string', description: defaultDescription }, + password: { + type: 'boolean', + description: nls.localize('JsonSchema.input.password', "Controls if a password input is shown. Password input hides the typed text."), + }, } }, { diff --git a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts index 6f434fa44a7..be61dbdff44 100644 --- a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts @@ -13,19 +13,24 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class ConfigurationResolverService extends BaseConfigurationResolverService { constructor( @IEditorService editorService: IEditorService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IConfigurationService configurationService: IConfigurationService, @ICommandService commandService: ICommandService, @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IQuickInputService quickInputService: IQuickInputService ) { - super(process.env as IProcessEnvironment, editorService, environmentService, configurationService, commandService, workspaceContextService, quickInputService); + super({ + getExecPath: (): string | undefined => { + return environmentService.execPath; + } + }, process.env as IProcessEnvironment, editorService, configurationService, commandService, workspaceContextService, quickInputService); } } -registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); \ No newline at end of file +registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); 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 f8ece9854b7..c29168f0ac9 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({ getExecPath: () => undefined }, environmentService.userEnv, editorService, 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({ getExecPath: () => undefined }, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), 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({ getExecPath: () => undefined }, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), 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({ getExecPath: () => undefined }, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), 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({ getExecPath: () => undefined }, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), 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({ getExecPath: () => undefined }, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), 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({ getExecPath: () => undefined }, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), 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({ getExecPath: () => undefined }, environmentService.userEnv, new TestEditorServiceWithActiveEditor(), 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 { @@ -599,7 +609,8 @@ class MockInputsConfigurationService extends TestConfigurationService { id: 'input3', type: 'promptString', description: 'Enterinput3', - default: 'default input3' + default: 'default input3', + password: true }, { id: 'input4', @@ -618,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..325f4fd609f 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,17 +162,17 @@ 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()); }); } } class FileDecorationChangeEvent implements IResourceDecorationChangeEvent { - private readonly _data = TernarySearchTree.forPaths(); + private readonly _data = TernarySearchTree.forUris(); affectsResource(uri: URI): boolean { - return this._data.get(uri.toString()) || this._data.findSuperstr(uri.toString()) !== undefined; + return this._data.get(uri) || this._data.findSuperstr(uri) !== undefined; } static debouncer(last: FileDecorationChangeEvent | undefined, current: URI | URI[]) { @@ -182,11 +182,11 @@ class FileDecorationChangeEvent implements IResourceDecorationChangeEvent { if (Array.isArray(current)) { // many for (const uri of current) { - last._data.set(uri.toString(), true); + last._data.set(uri, true); } } else { // one - last._data.set(current.toString(), true); + last._data.set(current, true); } return last; @@ -202,7 +202,7 @@ class DecorationDataRequest { class DecorationProviderWrapper { - readonly data = TernarySearchTree.forPaths(); + readonly data = TernarySearchTree.forUris(); private readonly _dispoable: IDisposable; constructor( @@ -234,12 +234,12 @@ class DecorationProviderWrapper { } knowsAbout(uri: URI): boolean { - return Boolean(this.data.get(uri.toString())) || Boolean(this.data.findSuperstr(uri.toString())); + return Boolean(this.data.get(uri)) || Boolean(this.data.findSuperstr(uri)); } getOrRetrieve(uri: URI, includeChildren: boolean, callback: (data: IDecorationData, isChild: boolean) => void): void { - const key = uri.toString(); - let item = this.data.get(key); + + let item = this.data.get(uri); if (item === undefined) { // unknown -> trigger request @@ -253,7 +253,7 @@ class DecorationProviderWrapper { if (includeChildren) { // (resolved) children - const iter = this.data.findSuperstr(key); + const iter = this.data.findSuperstr(uri); if (iter) { for (let item = iter.next(); !item.done; item = iter.next()) { if (item.value && !(item.value instanceof DecorationDataRequest)) { @@ -267,10 +267,10 @@ class DecorationProviderWrapper { private _fetchData(uri: URI): IDecorationData | null { // check for pending request and cancel it - const pendingRequest = this.data.get(uri.toString()); + const pendingRequest = this.data.get(uri); if (pendingRequest instanceof DecorationDataRequest) { pendingRequest.source.cancel(); - this.data.delete(uri.toString()); + this.data.delete(uri); } const source = new CancellationTokenSource(); @@ -282,23 +282,23 @@ class DecorationProviderWrapper { } else { // async -> we have a result soon const request = new DecorationDataRequest(source, Promise.resolve(dataOrThenable).then(data => { - if (this.data.get(uri.toString()) === request) { + if (this.data.get(uri) === request) { this._keepItem(uri, data); } }).catch(err => { - if (!isPromiseCanceledError(err) && this.data.get(uri.toString()) === request) { - this.data.delete(uri.toString()); + if (!isPromiseCanceledError(err) && this.data.get(uri) === request) { + this.data.delete(uri); } })); - this.data.set(uri.toString(), request); + this.data.set(uri, request); return null; } } private _keepItem(uri: URI, data: IDecorationData | undefined): IDecorationData | null { const deco = data ? data : null; - const old = this.data.set(uri.toString(), deco); + const old = this.data.set(uri, deco); if (deco || old) { // only fire event when something changed this._uriEmitter.fire(uri); @@ -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 d481f2ee6ff..7dc7647ffbf 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -88,10 +88,14 @@ 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); + } + + protected async doShowSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { if (fileNamesOrResources.length === 0) { return ConfirmResult.DONT_SAVE; } diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts index f67f9aa064c..6b42535bff5 100644 --- a/src/vs/workbench/services/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts @@ -18,6 +18,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { fromNow } from 'vs/base/common/date'; export class DialogService implements IDialogService { @@ -121,18 +122,24 @@ export class DialogService implements IDialogService { } async about(): Promise { - const detail = nls.localize('aboutDetail', - "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", - this.productService.version || 'Unknown', - this.productService.commit || 'Unknown', - this.productService.date || 'Unknown', - navigator.userAgent - ); + const detailString = (useAgo: boolean): string => { + return nls.localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", + this.productService.version || 'Unknown', + this.productService.commit || 'Unknown', + this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown', + navigator.userAgent + ); + }; + + const detail = detailString(true); + const detailToCopy = detailString(false); + const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail, cancelId: 1 }); if (choice === 0) { - this.clipboardService.writeText(detail); + this.clipboardService.writeText(detailToCopy); } } } diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index e9e8c8fff36..0a3570ebd62 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); @@ -156,7 +156,7 @@ export class SimpleFileDialog { public async showOpenDialog(options: IOpenDialogOptions = {}): Promise { this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri); - this.userHome = await this.remotePathService.userHome; + this.userHome = await this.getUserHome(); const newOptions = this.getOptions(options); if (!newOptions) { return Promise.resolve(undefined); @@ -167,7 +167,7 @@ export class SimpleFileDialog { public async showSaveDialog(options: ISaveDialogOptions): Promise { this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri); - this.userHome = await this.remotePathService.userHome; + this.userHome = await this.getUserHome(); this.requiresTrailing = true; const newOptions = this.getOptions(options, true); if (!newOptions) { @@ -231,6 +231,13 @@ export class SimpleFileDialog { return this.remoteAgentEnvironment; } + private async getUserHome(): Promise { + if (this.scheme !== Schemas.file) { + return this.remotePathService.userHome; + } + return this.environmentService.userHome!; + } + private async pickResource(isSave: boolean = false): Promise { this.allowFolderSelection = !!this.options.canSelectFolders; this.allowFileSelection = !!this.options.canSelectFiles; @@ -290,6 +297,8 @@ export class SimpleFileDialog { function doResolve(dialog: SimpleFileDialog, uri: URI | undefined) { if (uri) { + uri = resources.addTrailingPathSeparator(uri, dialog.separator); // Ensures that c: is c:/ since this comes from user input and can be incorrect. + // To be consistent, we should never have a trailing path separator on directories (or anything else). Will not remove from c:/. uri = resources.removeTrailingPathSeparator(uri); } resolve(uri); diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index 43caf27af2e..969a1e5a5dc 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -23,6 +23,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { MessageBoxOptions } from 'electron'; +import { fromNow } from 'vs/base/common/date'; interface IMassagedMessageBoxOptions { @@ -216,17 +217,23 @@ class NativeDialogService implements IDialogService { } const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; - const detail = nls.localize('aboutDetail', - "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", - version, - product.commit || 'Unknown', - product.date || 'Unknown', - process.versions['electron'], - process.versions['chrome'], - process.versions['node'], - process.versions['v8'], - `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` - ); + + const detailString = (useAgo: boolean): string => { + return nls.localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", + version, + product.commit || 'Unknown', + product.date ? `${product.date}${useAgo ? ' (' + fromNow(new Date(product.date), true) + ')' : ''}` : 'Unknown', + process.versions['electron'], + process.versions['chrome'], + process.versions['node'], + process.versions['v8'], + `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` + ); + }; + + const detail = detailString(true); + const detailToCopy = detailString(false); const ok = nls.localize('okButton', "OK"); const copy = mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy")); @@ -249,7 +256,7 @@ class NativeDialogService implements IDialogService { }); if (buttons[result.response] === copy) { - this.clipboardService.writeText(detail); + this.clipboardService.writeText(detailToCopy); } } } diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index 14b4f05f932..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.showSaveConfirm(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 7010d19a62c..ea360f390d5 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -4,24 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IRevertOptions, SaveReason, EditorsOrder, isTextEditor } 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'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, FileChangeType, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { basename } from 'vs/base/common/resources'; +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, IOpenEditorOverrideEntry } 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 } 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'; @@ -30,6 +30,9 @@ import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserv import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { timeout } from 'vs/base/common/async'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { indexOfPath } from 'vs/base/common/extpath'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -57,26 +60,18 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - private fileInputFactory: IFileInputFactory; - private readonly openEditorHandlers: IOpenEditorOverrideHandler[] = []; - - private lastActiveEditor: IEditorInput | undefined = undefined; - - private readonly editorsObserver = this._register(this.instantiationService.createInstance(EditorsObserver)); - - private readonly editorInputCache = new ResourceMap(); - constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, @IFileService private readonly fileService: IFileService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { super(); - this.fileInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileInputFactory(); + this.onConfigurationUpdated(configurationService.getValue()); this.registerListeners(); } @@ -87,9 +82,27 @@ 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())); + + // File changes & operations + // Note: there is some duplication with the two file event handlers- Since we cannot always rely on the disk events + // carrying all necessary data in all environments, we also use the file operation events to make sure operations are handled. + // In any case there is no guarantee if the local event is fired first or the disk one. Thus, code must handle the case + // that the event ordering is random as well as might not carry all information needed. + this._register(this.fileService.onDidRunOperation(e => this.onDidRunFileOperation(e))); + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); + + // Configuration + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); } + //#region Editor & group event handlers + + private lastActiveEditor: IEditorInput | undefined = undefined; + private onEditorsRestored(): void { // Register listeners to each opened group @@ -151,31 +164,240 @@ export class EditorService extends Disposable implements EditorServiceImpl { }); } - private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void { - if (event.options && event.options.ignoreOverrides) { - return; + //#endregion + + //#region Visible Editors Change: Install file watchers for out of workspace resources that became visible + + private readonly activeOutOfWorkspaceWatchers = new ResourceMap(); + + private handleVisibleEditorsChange(): void { + const visibleOutOfWorkspaceResources = new ResourceMap(); + + for (const editor of this.visibleEditors) { + const resources = distinct(coalesce([ + toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), + toResource(editor, { supportSideBySide: SideBySideEditor.DETAILS }) + ]), resource => resource.toString()); + + for (const resource of resources) { + if (this.fileService.canHandleResource(resource) && !this.contextService.isInsideWorkspace(resource)) { + visibleOutOfWorkspaceResources.set(resource, resource); + } + } } - for (const handler of this.openEditorHandlers) { - const result = handler(event.editor, event.options, group); - const override = result?.override; - if (override) { - event.prevent((() => override.then(editor => withNullAsUndefined(editor)))); - break; + // Handle no longer visible out of workspace resources + this.activeOutOfWorkspaceWatchers.keys().forEach(resource => { + if (!visibleOutOfWorkspaceResources.get(resource)) { + dispose(this.activeOutOfWorkspaceWatchers.get(resource)); + this.activeOutOfWorkspaceWatchers.delete(resource); + } + }); + + // Handle newly visible out of workspace resources + visibleOutOfWorkspaceResources.forEach(resource => { + if (!this.activeOutOfWorkspaceWatchers.get(resource)) { + const disposable = this.fileService.watch(resource); + this.activeOutOfWorkspaceWatchers.set(resource, disposable); + } + }); + } + + //#endregion + + //#region File Changes: Move & Deletes to move or close opend editors + + private onDidRunFileOperation(e: FileOperationEvent): void { + + // Handle moves specially when file is opened + if (e.isOperation(FileOperation.MOVE)) { + this.handleMovedFile(e.resource, e.target.resource); + } + + // Handle deletes + if (e.isOperation(FileOperation.DELETE) || e.isOperation(FileOperation.MOVE)) { + this.handleDeletedFile(e.resource, false, e.target ? e.target.resource : undefined); + } + } + + private onDidFilesChange(e: FileChangesEvent): void { + if (e.gotDeleted()) { + this.handleDeletedFile(e, true); + } + } + + private handleMovedFile(source: URI, target: URI): void { + for (const group of this.editorGroupService.groups) { + let replacements: (IResourceEditorReplacement | IEditorReplacement)[] = []; + + for (const editor of group.editors) { + const resource = editor.resource; + if (!resource || !isEqualOrParent(resource, source)) { + continue; // not matching our resource + } + + // Determine new resulting target resource + let targetResource: URI; + if (source.toString() === resource.toString()) { + targetResource = target; // file got moved + } else { + const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); + const index = indexOfPath(resource.path, source.path, ignoreCase); + targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved + } + + // Delegate move() to editor instance + const moveResult = editor.move(group.id, targetResource); + if (!moveResult) { + return; // not target - ignore + } + + const optionOverrides = { + preserveFocus: true, + pinned: group.isPinned(editor), + index: group.getIndexOfEditor(editor), + inactive: !group.isActive(editor) + }; + + // Construct a replacement with our extra options mixed in + if (moveResult.editor instanceof EditorInput) { + replacements.push({ + editor, + replacement: moveResult.editor, + options: { + ...moveResult.options, + ...optionOverrides + } + }); + } else { + replacements.push({ + editor: { resource: editor.resource }, + replacement: { + ...moveResult.editor, + options: { + ...moveResult.editor.options, + ...optionOverrides + } + } + }); + } + } + + // Apply replacements + if (replacements.length) { + this.replaceEditors(replacements, group); } } } - get activeControl(): IVisibleEditor | undefined { - return this.editorGroupService.activeGroup?.activeControl; + private closeOnFileDelete: boolean = false; + 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; + } else { + this.closeOnFileDelete = false; // default + } } - get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined { - const activeControl = this.activeControl; - if (activeControl) { - const activeControlWidget = activeControl.getControl(); - if (isCodeEditor(activeControlWidget) || isDiffEditor(activeControlWidget)) { - return activeControlWidget; + private handleDeletedFile(arg1: URI | FileChangesEvent, isExternal: boolean, movedTo?: URI): void { + for (const editor of this.getAllNonDirtyEditors({ includeUntitled: false, supportSideBySide: true })) { + (async () => { + const resource = editor.resource; + if (!resource) { + return; + } + + // Handle deletes in opened editors depending on: + // - 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.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 + // path but different casing. + if (movedTo && isEqualOrParent(resource, movedTo)) { + return; + } + + let matches = false; + if (arg1 instanceof FileChangesEvent) { + matches = arg1.contains(resource, FileChangeType.DELETED); + } else { + matches = isEqualOrParent(resource, arg1); + } + + if (!matches) { + return; + } + + // We have received reports of users seeing delete events even though the file still + // exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665). + // Since we do not want to close an editor without reason, we have to check if the + // file is really gone and not just a faulty file event. + // This only applies to external file events, so we need to check for the isExternal + // flag. + let exists = false; + if (isExternal && this.fileService.canHandleResource(resource)) { + await timeout(100); + exists = await this.fileService.exists(resource); + } + + if (!exists && !editor.isDisposed()) { + editor.dispose(); + } + } + })(); + } + } + + private getAllNonDirtyEditors(options: { includeUntitled: boolean, supportSideBySide: boolean }): IEditorInput[] { + const editors: IEditorInput[] = []; + + function conditionallyAddEditor(editor: IEditorInput): void { + if (editor.isUntitled() && !options.includeUntitled) { + return; + } + + if (editor.isDirty()) { + return; + } + + editors.push(editor); + } + + for (const editor of this.editors) { + if (options.supportSideBySide && editor instanceof SideBySideEditorInput) { + conditionallyAddEditor(editor.master); + conditionallyAddEditor(editor.details); + } else { + conditionallyAddEditor(editor); + } + } + + return editors; + } + + //#endregion + + //#region Editor accessors + + private readonly editorsObserver = this._register(this.instantiationService.createInstance(EditorsObserver)); + + get activeEditorPane(): IVisibleEditorPane | undefined { + return this.editorGroupService.activeGroup?.activeEditorPane; + } + + 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; } } @@ -185,11 +407,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; @@ -223,40 +445,71 @@ 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[] { return coalesce(this.editorGroupService.groups.map(group => group.activeEditor)); } + //#endregion + //#region preventOpenEditor() - overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { - this.openEditorHandlers.push(handler); + private readonly openEditorHandlers: IOpenEditorOverrideHandler[] = []; - return toDisposable(() => { - const index = this.openEditorHandlers.indexOf(handler); - if (index >= 0) { - this.openEditorHandlers.splice(index, 1); + overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { + const remove = insert(this.openEditorHandlers, handler); + + return toDisposable(() => remove()); + } + + getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { + const ret = []; + for (const handler of this.openEditorHandlers) { + const handlers = handler.getEditorOverrides ? handler.getEditorOverrides(editorInput, options, group).map(val => { return [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]; }) : []; + ret.push(...handlers); + } + + return ret; + } + + private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void { + if (event.options && event.options.ignoreOverrides) { + return; + } + + for (const handler of this.openEditorHandlers) { + const result = handler.open(event.editor, event.options, group); + const override = result?.override; + if (override) { + event.prevent((() => override.then(editor => withNullAsUndefined(editor)))); + break; } - }); + } } //#endregion //#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; @@ -267,7 +520,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; @@ -285,8 +538,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); @@ -418,9 +671,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[] = []; @@ -428,7 +681,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) }); } }); @@ -451,7 +704,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)); }); @@ -463,8 +716,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 @@ -489,8 +752,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) }); } @@ -509,9 +772,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; @@ -524,9 +787,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - //#region createInput() + //#region createEditorInput() - createInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditor): EditorInput { + private readonly editorInputCache = new ResourceMap(); + + createEditorInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditorInputType): EditorInput { // Typed Editor Input Support (EditorInput) if (input instanceof EditorInput) { @@ -539,25 +804,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, '↔'), @@ -568,7 +819,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, @@ -604,22 +855,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 @@ -629,12 +880,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); } } @@ -644,12 +895,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; @@ -679,8 +930,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { } private toSideBySideLabel(leftInput: EditorInput, rightInput: EditorInput, divider: string): string | undefined { - const leftResource = leftInput.getResource(); - const rightResource = rightInput.getResource(); + const leftResource = leftInput.resource; + const rightResource = rightInput.resource; // Without any resource, do not try to compute a label if (!leftResource || !rightResource) { @@ -690,7 +941,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 })}`; } @@ -709,6 +960,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 @@ -717,9 +972,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 { @@ -729,7 +984,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) { @@ -750,14 +1005,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 @@ -771,35 +1028,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); } @@ -817,16 +1073,37 @@ 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 { + super.dispose(); + + // Dispose remaining watchers if any + this.activeOutOfWorkspaceWatchers.forEach(disposable => dispose(disposable)); + this.activeOutOfWorkspaceWatchers.clear(); + } } 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; } /** @@ -843,25 +1120,28 @@ 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 { + getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { + return []; + } + + 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)); @@ -876,20 +1156,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); } @@ -899,19 +1179,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..360e7434fbe 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; @@ -26,8 +26,16 @@ export type ACTIVE_GROUP_TYPE = typeof ACTIVE_GROUP; export const SIDE_GROUP = -2; export type SIDE_GROUP_TYPE = typeof SIDE_GROUP; +export interface IOpenEditorOverrideEntry { + id: string; + label: string; + active: boolean; + detail?: string; +} + export interface IOpenEditorOverrideHandler { - (editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined; + open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, id?: string): IOpenEditorOverride | undefined; + getEditorOverrides?(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[]; } export interface IOpenEditorOverride { @@ -36,12 +44,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 +52,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 +60,7 @@ export interface IBaseSaveRevertAllEditorOptions { /** * Whether to include untitled editors as well. */ - includeUntitled?: boolean; + readonly includeUntitled?: boolean; } export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRevertAllEditorOptions { } @@ -71,17 +74,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 +101,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 +167,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 +182,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,9 +200,20 @@ 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; + /** + * Get all available editor overrides for the editor input. + */ + getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][]; + /** * Allows to override the opening of editors by installing a handler that will * be called each time an editor is about to open allowing to override the @@ -214,25 +229,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 ffcd2cb6215..6a015f24b33 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -4,90 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, ITestInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, GroupLocation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, IFileEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorOptions, CloseDirection, IEditorPartOptions, EditorsOrder } from 'vs/workbench/common/editor'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { EditorOptions, CloseDirection, IEditorPartOptions, EditorsOrder } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/browser/editor'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; const TEST_EDITOR_ID = 'MyFileEditorForEditorGroupService'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorGroupService'; -class TestEditorControl extends BaseEditor { - - constructor(@ITelemetryService telemetryService: ITelemetryService) { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - super.setInput(input, options, token); - - await input.resolve(); - } - - getId(): string { return TEST_EDITOR_ID; } - layout(): void { } - createEditor(): any { } -} - -class TestEditorInput extends EditorInput implements IFileEditorInput { - - constructor(private resource: URI) { super(); } - - getTypeId() { return TEST_EDITOR_INPUT_ID; } - resolve(): Promise { return Promise.resolve(null); } - matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - getResource(): URI { return this.resource; } - setForceOpenAsBinary(): void { } -} - suite('EditorGroupsService', () => { let disposables: IDisposable[] = []; setup(() => { - interface ISerializedTestEditorInput { - resource: string; - } - - class TestEditorInputFactory implements IEditorInputFactory { - - canSerialize(editorInput: EditorInput): boolean { - return true; - } - - serialize(editorInput: EditorInput): string { - const testEditorInput = editorInput; - const testInput: ISerializedTestEditorInput = { - resource: testEditorInput.getResource().toString() - }; - - return JSON.stringify(testInput); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { - const testInput: ISerializedTestEditorInput = JSON.parse(serializedEditorInput); - - return new TestEditorInput(URI.parse(testInput.resource)); - } - } - - disposables.push((Registry.as(EditorExtensions.EditorInputFactories)).registerEditorInputFactory(TEST_EDITOR_INPUT_ID, TestEditorInputFactory)); - disposables.push((Registry.as(Extensions.Editors)).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)])); + disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_EDITOR_INPUT_ID)); }); teardown(() => { @@ -95,18 +28,16 @@ suite('EditorGroupsService', () => { disposables = []; }); - function createPart(): EditorPart { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); + function createPart(instantiationService = workbenchInstantiationService()): [TestEditorPart, ITestInstantiationService] { + const part = instantiationService.createInstance(TestEditorPart); part.create(document.createElement('div')); part.layout(400, 300); - return part; + return [part, instantiationService]; } test('groups basics', async function () { - const part = createPart(); + const [part] = createPart(); let activeGroupChangeCounter = 0; const activeGroupChangeListener = part.onDidActiveGroupChange(() => { @@ -192,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'); @@ -267,8 +198,37 @@ suite('EditorGroupsService', () => { part.dispose(); }); + test('save & restore state', async function () { + let [part, instantiationService] = createPart(); + + const rootGroup = part.groups[0]; + const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); + + const rootGroupInput = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + await rootGroup.openEditor(rootGroupInput, EditorOptions.create({ pinned: true })); + + const rightGroupInput = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + await rightGroup.openEditor(rightGroupInput, EditorOptions.create({ pinned: true })); + + assert.equal(part.groups.length, 3); + + part.saveState(); + part.dispose(); + + let [restoredPart] = createPart(instantiationService); + + assert.equal(restoredPart.groups.length, 3); + assert.ok(restoredPart.getGroup(rootGroup.id)); + assert.ok(restoredPart.getGroup(rightGroup.id)); + assert.ok(restoredPart.getGroup(downGroup.id)); + + restoredPart.clearState(); + restoredPart.dispose(); + }); + test('groups index / labels', function () { - const part = createPart(); + const [part] = createPart(); const rootGroup = part.groups[0]; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -326,7 +286,7 @@ suite('EditorGroupsService', () => { }); test('copy/merge groups', async () => { - const part = createPart(); + const [part] = createPart(); let groupAddedCounter = 0; const groupAddedListener = part.onDidAddGroup(() => { @@ -344,17 +304,17 @@ suite('EditorGroupsService', () => { rootGroupDisposed = true; }); - const input = new TestEditorInput(URI.file('foo/bar')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input, EditorOptions.create({ pinned: true })); const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT, { activate: true }); const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN); assert.equal(groupAddedCounter, 2); assert.equal(downGroup.count, 1); - assert.ok(downGroup.activeEditor instanceof TestEditorInput); + assert.ok(downGroup.activeEditor instanceof TestFileEditorInput); part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.COPY_EDITORS }); assert.equal(rightGroup.count, 1); - assert.ok(rightGroup.activeEditor instanceof TestEditorInput); + assert.ok(rightGroup.activeEditor instanceof TestFileEditorInput); part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.MOVE_EDITORS }); assert.equal(rootGroup.count, 0); part.mergeGroup(rootGroup, downGroup); @@ -367,7 +327,7 @@ suite('EditorGroupsService', () => { }); test('whenRestored', async () => { - const part = createPart(); + const [part] = createPart(); await part.whenRestored; assert.ok(true); @@ -375,7 +335,7 @@ suite('EditorGroupsService', () => { }); test('options', () => { - const part = createPart(); + const [part] = createPart(); let oldOptions!: IEditorPartOptions; let newOptions!: IEditorPartOptions; @@ -396,7 +356,7 @@ suite('EditorGroupsService', () => { }); test('editor basics', async function () { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -437,8 +397,8 @@ suite('EditorGroupsService', () => { editorWillCloseCounter++; }); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditor(input, EditorOptions.create({ pinned: true })); await group.openEditor(inputInactive, EditorOptions.create({ inactive: true })); @@ -465,7 +425,7 @@ suite('EditorGroupsService', () => { assert.ok(!group.previewEditor); assert.equal(group.activeEditor, input); - assert.ok(group.activeControl instanceof TestEditorControl); + assert.equal(group.activeEditorPane?.getId(), TEST_EDITOR_ID); assert.equal(group.count, 2); const mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); @@ -494,12 +454,12 @@ suite('EditorGroupsService', () => { }); test('openEditors / closeEditors', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); @@ -512,13 +472,13 @@ suite('EditorGroupsService', () => { }); test('closeEditors (except one)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input1 = new TestEditorInput(URI.file('foo/bar1')); - const input2 = new TestEditorInput(URI.file('foo/bar2')); - const input3 = new TestEditorInput(URI.file('foo/bar3')); + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); @@ -533,13 +493,13 @@ suite('EditorGroupsService', () => { }); test('closeEditors (saved only)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input1 = new TestEditorInput(URI.file('foo/bar1')); - const input2 = new TestEditorInput(URI.file('foo/bar2')); - const input3 = new TestEditorInput(URI.file('foo/bar3')); + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); @@ -553,13 +513,13 @@ suite('EditorGroupsService', () => { }); test('closeEditors (direction: right)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input1 = new TestEditorInput(URI.file('foo/bar1')); - const input2 = new TestEditorInput(URI.file('foo/bar2')); - const input3 = new TestEditorInput(URI.file('foo/bar3')); + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); @@ -575,13 +535,13 @@ suite('EditorGroupsService', () => { }); test('closeEditors (direction: left)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input1 = new TestEditorInput(URI.file('foo/bar1')); - const input2 = new TestEditorInput(URI.file('foo/bar2')); - const input3 = new TestEditorInput(URI.file('foo/bar3')); + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); @@ -597,12 +557,12 @@ suite('EditorGroupsService', () => { }); test('closeAllEditors', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); @@ -615,12 +575,12 @@ suite('EditorGroupsService', () => { }); test('moveEditor (same group)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); let editorMoveCounter = 0; const editorGroupChangeListener = group.onDidGroupChange(e => { @@ -643,14 +603,14 @@ suite('EditorGroupsService', () => { }); test('moveEditor (across groups)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); @@ -665,14 +625,14 @@ suite('EditorGroupsService', () => { }); test('copyEditor (across groups)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); @@ -688,12 +648,12 @@ suite('EditorGroupsService', () => { }); test('replaceEditors', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditor(input); assert.equal(group.count, 1); @@ -706,7 +666,7 @@ suite('EditorGroupsService', () => { }); test('find neighbour group (left/right)', function () { - const part = createPart(); + const [part] = createPart(); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -717,7 +677,7 @@ suite('EditorGroupsService', () => { }); test('find neighbour group (up/down)', function () { - const part = createPart(); + const [part] = createPart(); const rootGroup = part.activeGroup; const downGroup = part.addGroup(rootGroup, GroupDirection.DOWN); @@ -728,7 +688,7 @@ suite('EditorGroupsService', () => { }); test('find group by location (left/right)', function () { - const part = createPart(); + const [part] = createPart(); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); @@ -744,4 +704,24 @@ suite('EditorGroupsService', () => { part.dispose(); }); + + test('applyLayout (2x2)', function () { + const [part] = createPart(); + + part.applyLayout({ groups: [{ groups: [{}, {}] }, { groups: [{}, {}] }], orientation: GroupOrientation.HORIZONTAL }); + + assert.equal(part.groups.length, 4); + + part.dispose(); + }); + + test('centeredLayout', function () { + const [part] = createPart(); + + part.centerLayout(true); + + assert.equal(part.isLayoutCentered(), true); + + part.dispose(); + }); }); 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 fbeff1ea8fc..11d828ea967 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -4,100 +4,34 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; +import { EditorActivation } from 'vs/platform/editor/common/editor'; 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, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IEditorInput } from 'vs/workbench/common/editor'; -import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { EditorInput, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; +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'; import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { Registry } from 'vs/platform/registry/common/platform'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { timeout } from 'vs/base/common/async'; import { toResource } from 'vs/base/test/common/utils'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; 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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +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'; -class TestEditorControl extends BaseEditor { - - constructor(@ITelemetryService telemetryService: ITelemetryService) { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - super.setInput(input, options, token); - - await input.resolve(); - } - - getId(): string { return TEST_EDITOR_ID; } - layout(): void { } - createEditor(): any { } -} - -class TestEditorInput extends EditorInput implements IFileEditorInput { - gotDisposed = false; - gotSaved = false; - gotSavedAs = false; - gotReverted = false; - dirty = false; - private fails = false; - constructor(public resource: URI) { super(); } - - getTypeId() { return TEST_EDITOR_INPUT_ID; } - resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } - matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - getResource(): URI { return this.resource; } - setForceOpenAsBinary(): void { } - setFailToOpen(): void { - this.fails = true; - } - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.gotSaved = true; - return this; - } - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.gotSavedAs = true; - return this; - } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - this.gotReverted = true; - this.gotSaved = false; - this.gotSavedAs = false; - return true; - } - isDirty(): boolean { - return this.dirty; - } - isReadonly(): boolean { - return false; - } - dispose(): void { - super.dispose(); - this.gotDisposed = true; - } -} - class FileServiceProvider extends Disposable { constructor(scheme: string, @IFileService fileService: IFileService) { super(); @@ -111,7 +45,7 @@ suite('EditorService', () => { let disposables: IDisposable[] = []; setup(() => { - disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)])); + disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_EDITOR_INPUT_ID)); }); teardown(() => { @@ -119,7 +53,7 @@ suite('EditorService', () => { disposables = []; }); - function createEditorService(): [EditorPart, EditorService, IInstantiationService] { + function createEditorService(): [EditorPart, EditorService, TestServiceAccessor] { const instantiationService = workbenchInstantiationService(); const part = instantiationService.createInstance(EditorPart); @@ -131,14 +65,14 @@ suite('EditorService', () => { const editorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - return [part, editorService, instantiationService]; + return [part, editorService, instantiationService.createInstance(TestServiceAccessor)]; } test('basics', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics')); - let otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics')); + let input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + let otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEventCounter = 0; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -160,18 +94,19 @@ suite('EditorService', () => { // Open input let editor = await service.openEditor(input, { pinned: true }); - assert.ok(editor instanceof TestEditorControl); - assert.equal(editor, service.activeControl); + assert.equal(editor?.getId(), TEST_EDITOR_ID); + 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); @@ -191,8 +126,8 @@ suite('EditorService', () => { assert.equal(0, service.count); // Open again 2 inputs (recreate because disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics')); + input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); await service.openEditor(input, { pinned: true }); editor = await service.openEditor(otherInput, { pinned: true }); @@ -202,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); @@ -216,12 +153,59 @@ suite('EditorService', () => { part.dispose(); }); - test('openEditors() / replaceEditors()', async () => { - const [part, service, testInstantiationService] = createEditorService(); + test('isOpen() with side by side editor', async () => { + const [part, service] = createEditorService(); - const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-openEditors')); - const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openEditors')); - const replaceInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource3-openEditors')); + 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(); + + const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const replaceInput = new TestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); await part.whenRestored; @@ -243,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); @@ -296,67 +280,78 @@ 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.getResource().fsPath, toResource.call(this, '/index.html').fsPath); + assert.strictEqual(contentInput.resource.fsPath, toResource.call(this, '/index.html').fsPath); + + // Typed 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 (diff) + input = service.createEditorInput({ + leftResource: toResource.call(this, '/master.html'), + rightResource: toResource.call(this, '/detail.html') + }); + assert(input instanceof DiffEditorInput); }); test('delegate', function (done) { @@ -374,7 +369,7 @@ suite('EditorService', () => { layout(): void { } - createEditor(): any { } + createEditor(): void { } } const ed = instantiationService.createInstance(MyEditor, 'my.editor'); @@ -392,9 +387,9 @@ suite('EditorService', () => { }); test('close editor does not dispose when editor opened in other group', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-close1')); + const input = new TestFileEditorInput(URI.parse('my://resource-close1'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -421,10 +416,10 @@ suite('EditorService', () => { }); test('open to the side', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); - const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; @@ -447,10 +442,10 @@ suite('EditorService', () => { }); test('editor group activation', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); - const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; @@ -482,10 +477,10 @@ suite('EditorService', () => { }); test('active editor change / visible editor change events', async function () { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - let otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + let otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEventFired = false; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -537,8 +532,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 2.) open, open same (forced open) (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -550,8 +545,8 @@ suite('EditorService', () => { await closeEditorAndWaitForNextToOpen(group, input); // 3.) open, open inactive, close (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -565,8 +560,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 4.) open, open inactive, close inactive (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -584,8 +579,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 5.) add group, remove group (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -607,8 +602,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 6.) open editor in inactive group (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -630,8 +625,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 7.) activate group (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -657,8 +652,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 8.) move editor (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -676,8 +671,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 9.) close editor in inactive group (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -702,9 +697,9 @@ suite('EditorService', () => { }); test('two active editor change events when opening editor to the side', async function () { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEvents = 0; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -738,7 +733,7 @@ suite('EditorService', () => { part.dispose(); }); - test('activeTextEditorWidget / activeTextEditorMode', async () => { + test('activeTextEditorControl / activeTextEditorMode', async () => { const [part, service] = createEditorService(); await part.whenRestored; @@ -746,19 +741,19 @@ 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(); }); test('openEditor returns NULL when opening fails or is inactive', async function () { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-inactive')); - const failingInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource3-failing')); + const input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + const otherInput = new TestFileEditorInput(URI.parse('my://resource2-inactive'), TEST_EDITOR_INPUT_ID); + const failingInput = new TestFileEditorInput(URI.parse('my://resource3-failing'), TEST_EDITOR_INPUT_ID); failingInput.setFailToOpen(); await part.whenRestored; @@ -776,12 +771,83 @@ suite('EditorService', () => { }); test('save, saveAll, revertAll', async function () { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); + const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + 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; + + await part.whenRestored; + + 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(); + }); + + test('file delete closes editor', async function () { + return testFileDeleteEditorClose(false); + }); + + test('file delete leaves dirty editors open', function () { + return testFileDeleteEditorClose(true); + }); + + async function testFileDeleteEditorClose(dirty: boolean): Promise { + const [part, service, accessor] = createEditorService(); + + const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + input1.dirty = dirty; + const input2 = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); + input2.dirty = dirty; const rootGroup = part.activeGroup; @@ -790,23 +856,131 @@ suite('EditorService', () => { await service.openEditor(input1, { pinned: true }); await service.openEditor(input2, { pinned: true }); - await service.save({ groupId: rootGroup.id, editor: input1 }); - assert.equal(input1.gotSaved, true); + assert.equal(rootGroup.activeEditor, input2); - await service.save({ groupId: rootGroup.id, editor: input1 }, { saveAs: true }); - assert.equal(input1.gotSavedAs, true); + const activeEditorChangePromise = awaitActiveEditorChange(service); + accessor.fileService.fireAfterOperation(new FileOperationEvent(input2.resource, FileOperation.DELETE)); + if (!dirty) { + await activeEditorChangePromise; + } - await service.revertAll(); - assert.equal(input1.gotReverted, true); + if (dirty) { + assert.equal(rootGroup.activeEditor, input2); + } else { + assert.equal(rootGroup.activeEditor, input1); + } - await service.saveAll(); - assert.equal(input1.gotSaved, true); - assert.equal(input2.gotSaved, true); + part.dispose(); + } - await service.saveAll({ saveAs: true }); - assert.equal(input1.gotSavedAs, true); - assert.equal(input2.gotSavedAs, true); + test('file move asks input to move', async function () { + const [part, service, accessor] = createEditorService(); + + const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const movedInput = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); + input1.movedEditor = { editor: movedInput }; + + const rootGroup = part.activeGroup; + + await part.whenRestored; + + await service.openEditor(input1, { pinned: true }); + + const activeEditorChangePromise = awaitActiveEditorChange(service); + accessor.fileService.fireAfterOperation(new FileOperationEvent(input1.resource, FileOperation.MOVE, { + resource: movedInput.resource, + ctime: 0, + etag: '', + isDirectory: false, + isFile: true, + mtime: 0, + name: 'resource2', + size: 0, + isSymbolicLink: false + })); + await activeEditorChangePromise; + + assert.equal(rootGroup.activeEditor, movedInput); part.dispose(); }); + + function awaitActiveEditorChange(editorService: IEditorService): Promise { + return new Promise(c => { + Event.once(editorService.onDidActiveEditorChange)(c); + }); + } + + test('file watcher gets installed for out of workspace files', async function () { + const [part, service, accessor] = createEditorService(); + + const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); + + await part.whenRestored; + + await service.openEditor(input1, { pinned: true }); + assert.equal(accessor.fileService.watches.length, 1); + assert.equal(accessor.fileService.watches[0].toString(), input1.resource.toString()); + + const editor = await service.openEditor(input2, { pinned: true }); + assert.equal(accessor.fileService.watches.length, 1); + assert.equal(accessor.fileService.watches[0].toString(), input2.resource.toString()); + + await editor?.group?.closeAllEditors(); + assert.equal(accessor.fileService.watches.length, 0); + + part.dispose(); + }); + + test('invokeWithinEditorContext', async function () { + const [part, service] = createEditorService(); + + const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); + new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); + + await part.whenRestored; + + await service.openEditor(input1, { pinned: true }); + + let hasAccessor = false; + service.invokeWithinEditorContext(accessor => { + hasAccessor = true; + }); + + assert.ok(hasAccessor); + + part.dispose(); + }); + + test('overrideOpenEditor', async function () { + const [part, service] = createEditorService(); + + const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); + + await part.whenRestored; + + let overrideCalled = false; + + const handler = service.overrideOpenEditor({ + open: editor => { + if (editor === input1) { + overrideCalled = true; + + return { override: service.openEditor(input2, { pinned: true }) }; + } + + return undefined; + } + }); + + await service.openEditor(input1, { pinned: true }); + + assert.ok(overrideCalled); + assert.equal(service.activeEditor, input2); + + handler.dispose(); + 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 b1129a2dc01..a9a0d5bc9e6 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -4,101 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorOptions, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputFactory, IFileEditorInput } from 'vs/workbench/common/editor'; +import { EditorOptions, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestStorageService } 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 { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { EditorActivation } from 'vs/platform/editor/common/editor'; import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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'; +import { isWeb } from 'vs/base/common/platform'; const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver'; const TEST_SERIALIZABLE_EDITOR_INPUT_ID = 'testSerializableEditorInputForEditorsObserver'; -class TestEditorControl extends BaseEditor { - - constructor() { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - super.setInput(input, options, token); - - await input.resolve(); - } - - getId(): string { return TEST_EDITOR_ID; } - layout(): void { } - createEditor(): any { } -} - -class TestEditorInput extends EditorInput implements IFileEditorInput { - - private dirty = false; - - constructor(public resource: URI) { super(); } - - getTypeId() { return TEST_EDITOR_INPUT_ID; } - resolve(): Promise { return Promise.resolve(null); } - matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - getResource(): URI { return this.resource; } - setForceOpenAsBinary(): void { } - isDirty(): boolean { return this.dirty; } - setDirty(): void { this.dirty = true; } -} - -class EditorsObserverTestEditorInput extends TestEditorInput { - getTypeId() { return TEST_SERIALIZABLE_EDITOR_INPUT_ID; } -} - -interface ISerializedTestInput { - resource: string; -} - -class EditorsObserverTestEditorInputFactory implements IEditorInputFactory { - - canSerialize(editorInput: EditorInput): boolean { - return true; - } - - serialize(editorInput: EditorInput): string { - let testEditorInput = editorInput; - let testInput: ISerializedTestInput = { - resource: testEditorInput.resource.toString() - }; - - return JSON.stringify(testInput); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { - let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); - - return new EditorsObserverTestEditorInput(URI.parse(testInput.resource)); - } -} - suite('EditorsObserver', function () { let disposables: IDisposable[] = []; setup(() => { - disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(TEST_SERIALIZABLE_EDITOR_INPUT_ID, EditorsObserverTestEditorInputFactory)); - disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For Editors Observer'), [new SyncDescriptor(TestEditorInput), new SyncDescriptor(EditorsObserverTestEditorInput)])); + disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_SERIALIZABLE_EDITOR_INPUT_ID)); }); teardown(() => { @@ -106,11 +36,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); @@ -130,16 +60,16 @@ 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 EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); @@ -147,10 +77,11 @@ 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 EditorsObserverTestEditorInput(URI.parse('foo://bar2')); - const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + 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); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); @@ -163,6 +94,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 })); @@ -174,8 +107,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; @@ -184,11 +120,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(); @@ -204,7 +146,7 @@ suite('EditorsObserver', function () { const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); @@ -215,6 +157,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 })); @@ -224,10 +167,11 @@ 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 - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true })); @@ -239,6 +183,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(); @@ -246,21 +192,28 @@ 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(); }); - test('copy group', async () => { + test('copy group', async function () { + if (isWeb) { + this.skip(); + } const [part, observer] = await createEditorObserver(); - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); - const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + 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); const rootGroup = part.activeGroup; @@ -276,6 +229,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); @@ -294,6 +250,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(); }); @@ -303,9 +274,9 @@ suite('EditorsObserver', function () { const rootGroup = part.activeGroup; - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); - const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + 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); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -323,6 +294,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 }); @@ -337,7 +311,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(); }); @@ -346,9 +324,9 @@ suite('EditorsObserver', function () { const rootGroup = part.activeGroup; - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); - const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + 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); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -368,6 +346,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 }); @@ -382,7 +363,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(); }); @@ -391,7 +376,7 @@ suite('EditorsObserver', function () { const rootGroup = part.activeGroup; - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); @@ -403,6 +388,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 }); @@ -411,7 +397,9 @@ suite('EditorsObserver', function () { currentEditorsMRU = restoredObserver.editors; assert.equal(currentEditorsMRU.length, 0); + assert.equal(restoredObserver.hasEditor(input1.resource), false); + part.clearState(); part.dispose(); }); @@ -425,10 +413,10 @@ suite('EditorsObserver', function () { const rootGroup = part.activeGroup; const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input3 = new TestEditorInput(URI.parse('foo://bar3')); - const input4 = new TestEditorInput(URI.parse('foo://bar4')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID); + const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -440,6 +428,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 } }); @@ -451,8 +443,12 @@ 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 TestEditorInput(URI.parse('foo://bar5')); + const input5 = new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID); await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true })); assert.equal(rootGroup.count, 1); @@ -460,8 +456,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(); @@ -477,21 +477,25 @@ suite('EditorsObserver', function () { const rootGroup = part.activeGroup; const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input3 = new TestEditorInput(URI.parse('foo://bar3')); - const input4 = new TestEditorInput(URI.parse('foo://bar4')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID); + const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); 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 })); @@ -503,6 +507,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 } }); @@ -520,6 +528,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 b7ee69dc594..c7a2eda6c58 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -4,32 +4,25 @@ *--------------------------------------------------------------------------------------------*/ 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 { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IPath } from 'vs/platform/windows/common/windows'; +import { IWorkbenchEnvironmentService, IEnvironmentConfiguration } 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'; +import { onUnexpectedError } from 'vs/base/common/errors'; -// TODO@ben remove properties that are node/electron only -export class BrowserWindowConfiguration implements IWindowConfiguration { +export class BrowserEnvironmentConfiguration implements IEnvironmentConfiguration { constructor( private readonly options: IBrowserWorkbenchEnvironmentConstructionOptions, private readonly payload: Map | undefined, - private readonly environment: IWorkbenchEnvironmentService + private readonly backupHome: URI ) { } - //#region PROPERLY CONFIGURED IN DESKTOP + WEB - @memoize get sessionId(): string { return generateUuid(); } @@ -37,10 +30,7 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { get remoteAuthority(): string | undefined { return this.options.remoteAuthority; } @memoize - get connectionToken(): string | undefined { return this.options.connectionToken || this.getCookieValue('vscode-tkn'); } - - @memoize - get backupWorkspaceResource(): URI { return joinPath(this.environment.backupHome, this.options.workspaceId); } + get backupWorkspaceResource(): URI { return joinPath(this.backupHome, this.options.workspaceId); } @memoize get filesToOpenOrCreate(): IPath[] | undefined { @@ -54,49 +44,24 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { return undefined; } - // Currently unsupported in web - get filesToDiff(): IPath[] | undefined { return undefined; } + @memoize + get filesToDiff(): IPath[] | undefined { + if (this.payload) { + const fileToDiffDetail = this.payload.get('diffFileDetail'); + const fileToDiffMaster = this.payload.get('diffFileMaster'); + if (fileToDiffDetail && fileToDiffMaster) { + return [ + { fileUri: URI.parse(fileToDiffDetail) }, + { fileUri: URI.parse(fileToDiffMaster) } + ]; + } + } - //#endregion + return undefined; + } - - //#region TODO MOVE TO NODE LAYER - - _!: any[]; - - 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 - - private getCookieValue(name: string): string | undefined { - const m = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531 - - return m ? m.pop() : undefined; + get highContrast() { + return false; // could investigate to detect high contrast theme automatically } } @@ -110,13 +75,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: IEnvironmentConfiguration | undefined = undefined; + get configuration(): IEnvironmentConfiguration { + if (!this._configuration) { + this._configuration = new BrowserEnvironmentConfiguration(this.options, this.payload, this.backupHome); + } + + return this._configuration; + } @memoize get isBuilt(): boolean { return !!product.commit; } @@ -124,6 +97,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get logsPath(): string { return this.options.logsPath.path; } + get logLevel(): string | undefined { return this.payload?.get('logLevel'); } + @memoize get logFile(): URI { return joinPath(this.options.logsPath, 'window.log'); } @@ -137,17 +112,16 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); } @memoize - get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, '.sync'); } + get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); } @memoize - get settingsSyncPreviewResource(): URI { return joinPath(this.userDataSyncHome, 'settings.json'); } - - @memoize - get keybindingsSyncPreviewResource(): URI { return joinPath(this.userDataSyncHome, 'keybindings.json'); } + get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync'); } @memoize get userDataSyncLogResource(): URI { return joinPath(this.options.logsPath, 'userDataSync.log'); } + get sync(): 'on' | 'off' { return 'on'; } + @memoize get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); } @@ -160,6 +134,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment @memoize get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); } + @memoize + get serviceMachineIdResource(): URI { return joinPath(this.userRoamingDataHome, 'machineid'); } + private _extensionHostDebugEnvironment: IExtensionHostDebugEnvironment | undefined = undefined; get debugExtensionHost(): IExtensionHostDebugParams { if (!this._extensionHostDebugEnvironment) { @@ -193,9 +170,19 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return this._extensionHostDebugEnvironment.extensionTestsLocationURI; } + get extensionEnabledProposedApi(): string[] | undefined { + if (!this._extensionHostDebugEnvironment) { + this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment(); + } + + return this._extensionHostDebugEnvironment.extensionEnabledProposedApi; + } + + get disableExtensions() { return this.payload?.get('disableExtensions') === 'true'; } + @memoize get webviewExternalEndpoint(): string { - // TODO: get fallback from product.json + // TODO@matt: get fallback from product.json return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}').replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); } @@ -209,76 +196,20 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return this.webviewExternalEndpoint.replace('{{uuid}}', '*'); } - // Currently not configurable in web - 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; } + get disableTelemetry(): 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 + get verbose(): boolean { return this.payload?.get('verbose') === 'true'; } + get logExtensionHostCommunication(): boolean { return this.payload?.get('logExtensionHostCommunication') === 'true'; } private payload: Map | undefined; constructor(readonly options: IBrowserWorkbenchEnvironmentConstructionOptions) { if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { - this.payload = serializableToMap(options.workspaceProvider.payload); + try { + this.payload = new Map(options.workspaceProvider.payload); + } catch (error) { + onUnexpectedError(error); // possible invalid payload for map + } } } @@ -310,6 +241,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment extensionHostDebugEnvironment.params.port = parseInt(value); extensionHostDebugEnvironment.params.break = true; break; + case 'enableProposedApi': + extensionHostDebugEnvironment.extensionEnabledProposedApi = []; + break; } } } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 4728f3e5102..bc2b16071f8 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -11,11 +11,15 @@ import { URI } from 'vs/base/common/uri'; export const IWorkbenchEnvironmentService = createDecorator('environmentService'); +export interface IEnvironmentConfiguration extends IWindowConfiguration { + backupWorkspaceResource?: URI; +} + export interface IWorkbenchEnvironmentService extends IEnvironmentService { _serviceBrand: undefined; - readonly configuration: IWindowConfiguration; + readonly configuration: IEnvironmentConfiguration; readonly options?: IWorkbenchConstructionOptions; diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index 8d32aff4ee6..6bc198008c0 100644 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -3,17 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -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 { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IWorkbenchEnvironmentService, IEnvironmentConfiguration } from 'vs/workbench/services/environment/common/environmentService'; import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; 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, INativeEnvironmentService { + + readonly configuration: INativeEnvironmentConfiguration; + + readonly disableCrashReporter: boolean; + + readonly cliPath: string; + + readonly log?: string; + readonly extHostLogsPath: URI; + + readonly userHome: URI; +} + +export interface INativeEnvironmentConfiguration extends IEnvironmentConfiguration, INativeWindowConfiguration { } + +export class NativeWorkbenchEnvironmentService extends EnvironmentService implements INativeWorkbenchEnvironmentService { _serviceBrand: undefined; @@ -34,12 +50,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: INativeEnvironmentConfiguration, + 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/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 89f2a5ff07d..3bde95c88b5 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -9,6 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { IExtension } from 'vs/platform/extensions/common/extensions'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IStringDictionary } from 'vs/base/common/collections'; export const IExtensionManagementServerService = createDecorator('extensionManagementServerService'); @@ -87,31 +88,40 @@ export type DynamicRecommendation = 'dynamic'; export type ExecutableRecommendation = 'executable'; export type CachedRecommendation = 'cached'; export type ApplicationRecommendation = 'application'; -export type ExtensionRecommendationSource = IWorkspace | IWorkspaceFolder | URI | DynamicRecommendation | ExecutableRecommendation | CachedRecommendation | ApplicationRecommendation; +export type ExperimentalRecommendation = 'experimental'; +export type ExtensionRecommendationSource = IWorkspace | IWorkspaceFolder | URI | DynamicRecommendation | ExecutableRecommendation | CachedRecommendation | ApplicationRecommendation | ExperimentalRecommendation; export interface IExtensionRecommendation { extensionId: string; sources: ExtensionRecommendationSource[]; } -export const IExtensionTipsService = createDecorator('extensionTipsService'); - -export interface IExtensionTipsService { - _serviceBrand: undefined; - getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; }; - getFileBasedRecommendations(): IExtensionRecommendation[]; - getOtherRecommendations(): Promise; - getWorkspaceRecommendations(): Promise; - getKeymapRecommendations(): IExtensionRecommendation[]; - toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void; - getAllIgnoredRecommendations(): { global: string[], workspace: string[] }; - onRecommendationChange: Event; -} - export const enum ExtensionRecommendationReason { Workspace, File, Executable, DynamicWorkspace, - Experimental + Experimental, + Application, +} + +export interface IExtensionRecommendationReson { + reasonId: ExtensionRecommendationReason; + reasonText: string; +} + +export const IExtensionRecommendationsService = createDecorator('extensionRecommendationsService'); + +export interface IExtensionRecommendationsService { + _serviceBrand: undefined; + + getAllRecommendationsWithReason(): IStringDictionary; + getFileBasedRecommendations(): IExtensionRecommendation[]; + getOtherRecommendations(): Promise; + getWorkspaceRecommendations(): Promise; + getKeymapRecommendations(): IExtensionRecommendation[]; + + toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void; + getIgnoredRecommendations(): ReadonlyArray; + onRecommendationChange: Event; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 4a07843244d..9f8c6ac6f5e 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -209,7 +209,7 @@ export class ExtensionManagementService extends Disposable implements IExtension return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name)); } if (!isLanguagePackExtension(manifest) && !canExecuteOnWorkspace(manifest, this.productService, this.configurationService)) { - const error = new Error(localize('cannot be installed', "Cannot install '{0}' extension since it cannot be enabled in the remote server.", gallery.displayName || gallery.name)); + const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension has defined that it cannot run on the remote server.", gallery.displayName || gallery.name)); error.name = INSTALL_ERROR_NOT_SUPPORTED; return Promise.reject(error); } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts b/src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts new file mode 100644 index 00000000000..286af44c7d7 --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement'; + +class WebExtensionTipsService implements IExtensionTipsService { + + _serviceBrand: any; + + constructor() { } + + async getImportantExecutableBasedTips(): Promise { + return []; + } + + async getOtherExecutableBasedTips(): Promise { + return []; + } + + async getAllWorkspacesTips(): Promise { + return []; + } + +} + +registerSingleton(IExtensionTipsService, WebExtensionTipsService); diff --git a/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts new file mode 100644 index 00000000000..6c53d8ad990 --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement'; + +class NativeExtensionTipsService implements IExtensionTipsService { + + _serviceBrand: any; + + private readonly channel: IChannel; + + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService + ) { + this.channel = sharedProcessService.getChannel('extensionTipsService'); + } + + getImportantExecutableBasedTips(): Promise { + return this.channel.call('getImportantExecutableBasedTips'); + } + + getOtherExecutableBasedTips(): Promise { + return this.channel.call('getOtherExecutableBasedTips'); + } + + getAllWorkspacesTips(): Promise { + return this.channel.call('getAllWorkspacesTips'); + } + +} + +registerSingleton(IExtensionTipsService, NativeExtensionTipsService); 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/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 41423b188fa..01d63f94a76 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -13,7 +13,7 @@ import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementSer import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IURLHandler, IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -26,6 +26,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -100,7 +101,8 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IHostService private readonly hostService: IHostService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IStorageService private readonly storageService: IStorageService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProgressService private readonly progressService: IProgressService ) { this.storage = new ConfirmedExtensionIdStorage(storageService); @@ -273,32 +275,20 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return; } - let notificationHandle: INotificationHandle | null = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) }); - notificationHandle.progress.infinite(); - notificationHandle.onDidClose(() => notificationHandle = null); - try { - await this.extensionManagementService.installFromGallery(galleryExtension); - const reloadMessage = localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString()); - const reloadActionLabel = localize('Reload', "Reload Window and Open"); + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) + }, () => this.extensionManagementService.installFromGallery(galleryExtension)); - if (notificationHandle) { - notificationHandle.progress.done(); - notificationHandle.updateMessage(reloadMessage); - notificationHandle.updateActions({ - primary: [new Action('reloadWindow', reloadActionLabel, undefined, true, () => this.reloadAndHandle(uri))] - }); - } else { - this.notificationService.prompt(Severity.Info, reloadMessage, [{ label: reloadActionLabel, run: () => this.reloadAndHandle(uri) }], { sticky: true }); - } - } catch (e) { - if (notificationHandle) { - notificationHandle.progress.done(); - notificationHandle.updateSeverity(Severity.Error); - notificationHandle.updateMessage(e); - } else { - this.notificationService.error(e); - } + this.notificationService.prompt( + Severity.Info, + localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString()), + [{ label: localize('Reload', "Reload Window and Open"), run: () => this.reloadAndHandle(uri) }], + { sticky: true } + ); + } catch (error) { + this.notificationService.error(error); } } } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts index 5e099346248..097a0487931 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts @@ -135,8 +135,6 @@ export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { parentPid: -1, environment: { isExtensionDevelopmentDebug: false, - appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined, - appSettingsHome: this._environmentService.appSettingsHome ? this._environmentService.appSettingsHome : undefined, appName: this._productService.nameLong, appUriScheme: this._productService.urlProtocol, appLanguage: platform.language, 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/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 83ace18fc92..922362dce3e 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -14,7 +14,8 @@ import * as platform from 'vs/base/common/platform'; import { originalFSPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import product from 'vs/platform/product/common/product'; @@ -22,8 +23,6 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints'; import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { parseBuiltInExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; interface IExtensionCacheData { input: ExtensionScannerInput; @@ -55,10 +54,9 @@ export class CachedExtensionScanner { constructor( @INotificationService private readonly _notificationService: INotificationService, - @IEnvironmentService private readonly _environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, @IHostService private readonly _hostService: IHostService, - @IProductService private readonly _productService: IProductService, ) { this.scannedExtensions = new Promise((resolve, reject) => { this._scannedExtensionsResolve = resolve; @@ -81,7 +79,7 @@ export class CachedExtensionScanner { public async startScanningExtensions(log: ILog): Promise { try { const translations = await this.translationConfig; - const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._hostService, this._notificationService, this._environmentService, this._extensionEnablementService, this._productService, log, translations); + const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._hostService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations); let result = new Map(); system.forEach((systemExtension) => { @@ -114,7 +112,7 @@ export class CachedExtensionScanner { } } - private static async _validateExtensionsCache(hostService: IHostService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise { + private static async _validateExtensionsCache(hostService: IHostService, notificationService: INotificationService, environmentService: INativeWorkbenchEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); @@ -149,7 +147,7 @@ export class CachedExtensionScanner { ); } - private static async _readExtensionCache(environmentService: IEnvironmentService, cacheKey: string): Promise { + private static async _readExtensionCache(environmentService: INativeWorkbenchEnvironmentService, cacheKey: string): Promise { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); @@ -163,7 +161,7 @@ export class CachedExtensionScanner { return null; } - private static async _writeExtensionCache(environmentService: IEnvironmentService, cacheKey: string, cacheContents: IExtensionCacheData): Promise { + private static async _writeExtensionCache(environmentService: INativeWorkbenchEnvironmentService, cacheKey: string, cacheContents: IExtensionCacheData): Promise { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); @@ -180,7 +178,7 @@ export class CachedExtensionScanner { } } - private static async _scanExtensionsWithCache(hostService: IHostService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise { + private static async _scanExtensionsWithCache(hostService: IHostService, notificationService: INotificationService, environmentService: INativeWorkbenchEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise { if (input.devMode) { // Do not cache when running out of sources... return ExtensionScanner.scanExtensions(input, log); @@ -239,9 +237,8 @@ export class CachedExtensionScanner { private static _scanInstalledExtensions( hostService: IHostService, notificationService: INotificationService, - environmentService: IEnvironmentService, + environmentService: INativeWorkbenchEnvironmentService, extensionEnablementService: IWorkbenchExtensionEnablementService, - productService: IProductService, log: ILog, translations: Translations ): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> { @@ -263,9 +260,7 @@ export class CachedExtensionScanner { let finalBuiltinExtensions: Promise = builtinExtensions; if (devMode) { - const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json')); - const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8') - .then(raw => parseBuiltInExtensions(raw, productService.quality)); + const builtInExtensions = Promise.resolve(product.builtInExtensions || []); const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); const controlFile = pfs.readFile(controlFilePath, 'utf8') @@ -325,7 +320,6 @@ interface IBuiltInExtension { name: string; version: string; repo: string; - forQualities?: ReadonlyArray; } interface IBuiltInExtensionControl { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index c45f7e5c3e3..24e31de4b18 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, @@ -416,7 +417,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: URI.file(this._environmentService.globalStorageHome), - userHome: URI.file(this._environmentService.userHome), + userHome: this._environmentService.userHome, webviewResourceRoot: this._environmentService.webviewResourceRoot, webviewCspSource: this._environmentService.webviewCspSource, }, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts index 07ea1e8b2c6..914178135b7 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts @@ -30,10 +30,10 @@ export class ExtensionHostProfiler { } private distill(profile: Profile, extensions: IExtensionDescription[]): IExtensionHostProfile { - let searchTree = TernarySearchTree.forPaths(); + let searchTree = TernarySearchTree.forUris(); for (let extension of extensions) { if (extension.extensionLocation.scheme === Schemas.file) { - searchTree.set(URI.file(realpathSync(extension.extensionLocation.fsPath)).toString(), extension); + searchTree.set(URI.file(realpathSync(extension.extensionLocation.fsPath)), extension); } } @@ -62,7 +62,7 @@ export class ExtensionHostProfiler { } else if (segmentId === 'self' && node.callFrame.url) { let extension: IExtensionDescription | undefined; try { - extension = searchTree.findSubstr(URI.parse(node.callFrame.url).toString()); + extension = searchTree.findSubstr(URI.parse(node.callFrame.url)); } catch { // ignore } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index a3a45ed8ec4..0843da823ea 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -11,8 +11,8 @@ import { AbstractExtensionService } from 'vs/workbench/services/extensions/commo import * as nls from 'vs/nls'; import { runWhenIdle } from 'vs/base/common/async'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -35,12 +35,13 @@ 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'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; class DeltaExtensionsQueueItem { constructor( @@ -59,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, @@ -72,13 +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 + @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, + @IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService, ) { super( instantiationService, notificationService, - environmentService, + _environmentService, telemetryService, extensionEnablementService, fileService, @@ -359,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); @@ -441,13 +442,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten const remoteAuthority = this._environmentService.configuration.remoteAuthority; const extensionHost = this._extensionHostProcessManagers[0]; - let localExtensions = flatten(await Promise.all([this._extensionScanner.scannedExtensions, this._staticExtensions.getExtensions()])); + const allExtensions = flatten(await Promise.all([this._extensionScanner.scannedExtensions, this._staticExtensions.getExtensions()])); // enable or disable proposed API per extension - this._checkEnableProposedApi(localExtensions); + this._checkEnableProposedApi(allExtensions); // remove disabled extensions - localExtensions = remove(localExtensions, extension => this._isDisabled(extension)); + let localExtensions = remove(allExtensions, extension => this._isDisabled(extension)); if (remoteAuthority) { let resolvedAuthority: ResolverResult; @@ -455,15 +456,15 @@ export class ExtensionService extends AbstractExtensionService implements IExten try { resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority); } catch (err) { - console.error(err); - const plusIndex = remoteAuthority.indexOf('+'); - const authorityFriendlyName = plusIndex > 0 ? remoteAuthority.substr(0, plusIndex) : remoteAuthority; - if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) { - this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", authorityFriendlyName) }); + const remoteName = getRemoteName(remoteAuthority); + if (RemoteAuthorityResolverError.isNoResolverFound(err)) { + err.isHandled = await this._handleNoResolverFound(remoteName, allExtensions); } else { - console.log(`Not showing a notification for the error`); + console.log(err); + 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 @@ -578,6 +579,67 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._electronService.closeWindow(); } } + + private async _handleNoResolverFound(remoteName: string, allExtensions: IExtensionDescription[]): Promise { + const recommendation = this._productService.remoteExtensionTips?.[remoteName]; + if (!recommendation) { + return false; + } + const sendTelemetry = (userReaction: 'install' | 'enable' | 'cancel') => { + /* __GDPR__ + "remoteExtensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this._telemetryService.publicLog('remoteExtensionRecommendations:popup', { userReaction, extensionId: resolverExtensionId }); + }; + + const resolverExtensionId = recommendation.extensionId; + 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); + this._notificationService.prompt(Severity.Info, message, + [{ + label: nls.localize('enable', 'Enable and Reload'), + run: async () => { + sendTelemetry('enable'); + await this._extensionEnablementService.setEnablement([toExtension(extension)], EnablementState.EnabledGlobally); + await this._hostService.reload(); + } + }], + { sticky: true } + ); + } + } 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); + this._notificationService.prompt(Severity.Info, message, + [{ + label: nls.localize('install', 'Install and Reload'), + run: async () => { + sendTelemetry('install'); + const galleryExtension = await this._extensionGalleryService.getCompatibleExtension({ id: resolverExtensionId }); + if (galleryExtension) { + await this._extensionManagementService.installFromGallery(galleryExtension); + await this._hostService.reload(); + } else { + this._notificationService.error(nls.localize('resolverExtensionNotFound', "`{0}` not found on marketplace")); + } + + } + }], + { + sticky: true, + onCancel: () => sendTelemetry('cancel') + } + ); + + } + return true; + + } } function remove(arr: IExtensionDescription[], predicate: (item: IExtensionDescription) => boolean): IExtensionDescription[]; @@ -604,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, @@ -620,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 a17f83a44e9..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'; @@ -34,7 +32,6 @@ import { addDisposableListener, EventType, EventHelper } from 'vs/base/browser/d import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; -import { ILogService } from 'vs/platform/log/common/log'; /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -83,7 +80,7 @@ interface ISerializedEditorHistoryEntry { } interface IStackEntry { - input: IEditorInput | IResourceInput; + input: IEditorInput | IResourceEditorInput; selection?: Selection; } @@ -99,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, @@ -112,8 +109,7 @@ export class HistoryService extends Disposable implements IHistoryService { @IWorkspacesService private readonly workspacesService: IWorkspacesService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @ILogService private readonly logService: ILogService + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); @@ -125,14 +121,14 @@ export class HistoryService extends Disposable implements IHistoryService { this._register(this.editorService.onDidOpenEditorFail(event => this.remove(event.editor))); this._register(this.editorService.onDidCloseEditor(event => this.onEditorClosed(event))); this._register(this.storageService.onWillSaveState(() => this.saveState())); - this._register(this.fileService.onFileChanges(event => this.onFileChanges(event))); - this._register(this.resourceFilter.onExpressionChange(() => this.removeExcludedFromHistory())); + this._register(this.fileService.onDidFilesChange(event => this.onDidFilesChange(event))); + 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(); } @@ -142,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))); } }; @@ -171,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; } @@ -220,17 +216,17 @@ export class HistoryService extends Disposable implements IHistoryService { return identifier.editor.matches(editor.input); } - private onFileChanges(e: FileChangesEvent): void { + private onDidFilesChange(e: FileChangesEvent): void { if (e.gotDeleted()) { this.remove(e); // remove from history files that got deleted or moved } } - 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); } @@ -247,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) { @@ -257,27 +253,29 @@ 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]); } clear(): void { - this.ensureHistoryLoaded(); + + // History + this.clearRecentlyOpened(); // Navigation (next, previous) this.navigationStackIndex = -1; @@ -289,9 +287,6 @@ export class HistoryService extends Disposable implements IHistoryService { // Closed files this.recentlyClosedFiles = []; - // History - this.clearRecentlyOpened(); - // Context Keys this.updateContextKeys(); } @@ -344,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, @@ -358,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 @@ -395,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; } @@ -416,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; } @@ -461,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[] = []; @@ -502,16 +497,16 @@ 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 { - const resource = input.getResource(); + 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 // issues such as https://github.com/microsoft/vscode/issues/85204 @@ -533,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); @@ -551,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) { @@ -567,26 +562,26 @@ 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); } if (arg2 instanceof EditorInput) { - const inputResource = arg2.getResource(); + const inputResource = arg2.resource; if (!inputResource) { return false; } @@ -598,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 @@ -615,7 +610,7 @@ export class HistoryService extends Disposable implements IHistoryService { // Track closing of editor to support to reopen closed editors (unless editor was replaced) if (!event.replaced) { - const resource = event.editor ? event.editor.getResource() : undefined; + const resource = event.editor ? event.editor.resource : undefined; const supportsReopen = resource && this.fileService.canHandleResource(resource); // we only support file'ish things to reopen if (resource && supportsReopen) { @@ -661,7 +656,7 @@ export class HistoryService extends Disposable implements IHistoryService { private containsRecentlyClosedFile(group: IEditorGroup, recentlyClosedEditor: IRecentlyClosedFile): boolean { for (const editor of group.editors) { - if (isEqual(editor.getResource(), recentlyClosedEditor.resource)) { + if (isEqual(editor.resource, recentlyClosedEditor.resource)) { return true; } } @@ -669,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); } @@ -680,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); } @@ -719,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 = []; - private loaded = false; - 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 history: Array | undefined = undefined; - private getExcludes(root?: URI): IExpression { - const scope = root ? { resource: root } : undefined; + private readonly resourceExcludeMatcher = this._register(createResourceExcludeMatcher(this.instantiationService, this.configurationService)); - 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; @@ -741,11 +726,10 @@ export class HistoryService extends Disposable implements IHistoryService { return; } - this.ensureHistoryLoaded(); - - const historyInput = this.preferResourceInput(input); + const historyInput = this.preferResourceEditorInput(input); // Remove any existing entry and add to the beginning + this.ensureHistoryLoaded(this.history); this.removeFromHistory(input); this.history.unshift(historyInput); @@ -761,18 +745,18 @@ 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 { - this.ensureHistoryLoaded(); + this.ensureHistoryLoaded(this.history); this.history = this.history.filter(e => { const include = this.include(e); @@ -786,8 +770,8 @@ export class HistoryService extends Disposable implements IHistoryService { }); } - private removeFromHistory(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { - this.ensureHistoryLoaded(); + private removeFromHistory(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { + this.ensureHistoryLoaded(this.history); this.history = this.history.filter(e => { const matches = this.matches(arg1, e); @@ -808,18 +792,60 @@ export class HistoryService extends Disposable implements IHistoryService { this.editorHistoryListeners.clear(); } - getHistory(): ReadonlyArray { - this.ensureHistoryLoaded(); + getHistory(): ReadonlyArray { + this.ensureHistoryLoaded(this.history); return this.history.slice(0); } - private ensureHistoryLoaded(): void { - if (!this.loaded) { - this.loadHistory(); + private ensureHistoryLoaded(history: Array | undefined): asserts history { + if (!this.history) { + this.history = this.loadHistory(); + } + } + + private loadHistory(): Array { + let entries: ISerializedEditorHistoryEntry[] = []; + + const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); + if (entriesRaw) { + entries = coalesce(JSON.parse(entriesRaw)); } - this.loaded = true; + const registry = Registry.as(EditorExtensions.EditorInputFactories); + + return coalesce(entries.map(entry => { + try { + return this.safeLoadHistoryEntry(registry, entry); + } catch (error) { + return undefined; // https://github.com/Microsoft/vscode/issues/60960 + } + })); + } + + private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceEditorInput | undefined { + const serializedEditorHistoryEntry = entry; + + // File resource: via URI.revive() + if (serializedEditorHistoryEntry.resourceJSON) { + return { resource: URI.revive(serializedEditorHistoryEntry.resourceJSON) }; + } + + // Editor input: via factory + const { editorInputJSON } = serializedEditorHistoryEntry; + if (editorInputJSON?.deserialized) { + const factory = registry.getEditorInputFactory(editorInputJSON.typeId); + if (factory) { + const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized); + if (input) { + this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners); + } + + return withNullAsUndefined(input); + } + } + + return undefined; } private saveState(): void { @@ -844,64 +870,15 @@ 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; })); - this.logService.trace(`[editor history] saving ${entries.length} entries`); this.storageService.store(HistoryService.HISTORY_STORAGE_KEY, JSON.stringify(entries), StorageScope.WORKSPACE); } - private loadHistory(): void { - let entries: ISerializedEditorHistoryEntry[] = []; - - const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); - if (entriesRaw) { - entries = coalesce(JSON.parse(entriesRaw)); - } - - const registry = Registry.as(EditorExtensions.EditorInputFactories); - - this.history = coalesce(entries.map(entry => { - try { - return this.safeLoadHistoryEntry(registry, entry); - } catch (error) { - this.logService.error(`[editor history] error loading one editor history entry: ${error.toString()}`); - - return undefined; // https://github.com/Microsoft/vscode/issues/60960 - } - })); - - this.logService.trace(`[editor history] loading ${this.history.length} entries`); - } - - private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined { - const serializedEditorHistoryEntry = entry; - - // File resource: via URI.revive() - if (serializedEditorHistoryEntry.resourceJSON) { - return { resource: URI.revive(serializedEditorHistoryEntry.resourceJSON) }; - } - - // Editor input: via factory - const { editorInputJSON } = serializedEditorHistoryEntry; - if (editorInputJSON?.deserialized) { - const factory = registry.getEditorInputFactory(editorInputJSON.typeId); - if (factory) { - const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized); - if (input) { - this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners); - } - - return withNullAsUndefined(input); - } - } - - return undefined; - } - //#endregion //#region Last Active Workspace/File @@ -925,18 +902,17 @@ export class HistoryService extends Disposable implements IHistoryService { } // Multiple folders: find the last active one - const history = this.getHistory(); - for (const input of history) { + for (const input of this.getHistory()) { if (input instanceof EditorInput) { 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; } @@ -954,13 +930,12 @@ export class HistoryService extends Disposable implements IHistoryService { } getLastActiveFile(filterByScheme: string): URI | undefined { - const history = this.getHistory(); - for (const input of history) { + for (const input of this.getHistory()) { let resource: URI | undefined; 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/history/test/browser/history.test.ts b/src/vs/workbench/services/history/test/browser/history.test.ts index 6ea008debf7..e2e5b97413a 100644 --- a/src/vs/workbench/services/history/test/browser/history.test.ts +++ b/src/vs/workbench/services/history/test/browser/history.test.ts @@ -4,21 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorOptions, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputFactory, IFileEditorInput } from 'vs/workbench/common/editor'; +import { EditorOptions } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor } from 'vs/workbench/test/browser/workbenchTestServices'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorGroupsService, GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { HistoryService } from 'vs/workbench/services/history/browser/history'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -27,68 +19,6 @@ import { timeout } from 'vs/base/common/async'; const TEST_EDITOR_ID = 'MyTestEditorForEditorHistory'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForHistoyService'; -const TEST_SERIALIZABLE_EDITOR_INPUT_ID = 'testSerializableEditorInputForHistoyService'; - -class TestEditorControl extends BaseEditor { - - constructor() { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - super.setInput(input, options, token); - - await input.resolve(); - } - - getId(): string { return TEST_EDITOR_ID; } - layout(): void { } - createEditor(): any { } -} - -class TestEditorInput extends EditorInput implements IFileEditorInput { - - constructor(public resource: URI) { super(); } - - getTypeId() { return TEST_EDITOR_INPUT_ID; } - resolve(): Promise { return Promise.resolve(null); } - matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - getResource(): URI { return this.resource; } - setForceOpenAsBinary(): void { } -} - -class HistoryTestEditorInput extends TestEditorInput { - getTypeId() { return TEST_SERIALIZABLE_EDITOR_INPUT_ID; } -} - -interface ISerializedTestInput { - resource: string; -} - -class HistoryTestEditorInputFactory implements IEditorInputFactory { - - canSerialize(editorInput: EditorInput): boolean { - return true; - } - - serialize(editorInput: EditorInput): string { - let testEditorInput = editorInput; - let testInput: ISerializedTestInput = { - resource: testEditorInput.resource.toString() - }; - - return JSON.stringify(testInput); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { - let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); - - return new HistoryTestEditorInput(URI.parse(testInput.resource)); - } -} async function createServices(): Promise<[EditorPart, HistoryService, EditorService]> { const instantiationService = workbenchInstantiationService(); @@ -115,8 +45,7 @@ suite('HistoryService', function () { let disposables: IDisposable[] = []; setup(() => { - disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(TEST_SERIALIZABLE_EDITOR_INPUT_ID, HistoryTestEditorInputFactory)); - disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For History Editor Service'), [new SyncDescriptor(TestEditorInput), new SyncDescriptor(HistoryTestEditorInput)])); + disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)])); }); teardown(() => { @@ -127,11 +56,11 @@ suite('HistoryService', function () { test('back / forward', async () => { const [part, historyService] = await createServices(); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); assert.equal(part.activeGroup.activeEditor, input1); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); assert.equal(part.activeGroup.activeEditor, input2); @@ -150,10 +79,10 @@ suite('HistoryService', function () { let history = historyService.getHistory(); assert.equal(history.length, 0); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); history = historyService.getHistory(); @@ -172,10 +101,10 @@ suite('HistoryService', function () { assert.ok(!historyService.getLastActiveFile('foo')); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - assert.equal(historyService.getLastActiveFile('foo')?.toString(), input1.getResource().toString()); + assert.equal(historyService.getLastActiveFile('foo')?.toString(), input1.resource.toString()); part.dispose(); }); @@ -183,8 +112,8 @@ suite('HistoryService', function () { test('open next/previous recently used editor (single group)', async () => { const [part, historyService] = await createServices(); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); assert.equal(part.activeGroup.activeEditor, input1); @@ -211,8 +140,8 @@ suite('HistoryService', function () { const [part, historyService] = await createServices(); const rootGroup = part.activeGroup; - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -233,10 +162,10 @@ suite('HistoryService', function () { test('open next/previous recently is reset when other input opens', async () => { const [part, historyService] = await createServices(); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input3 = new TestEditorInput(URI.parse('foo://bar3')); - const input4 = new TestEditorInput(URI.parse('foo://bar4')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID); + const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); 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 8bf8e567fe2..1bd4e2d252e 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -11,11 +11,11 @@ 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'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; @@ -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; @@ -510,7 +522,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let commandAction = MenuRegistry.getCommand(command); let precondition = commandAction && commandAction.precondition; - let fullWhen: ContextKeyExpr | undefined; + let fullWhen: ContextKeyExpression | undefined; if (when && precondition) { fullWhen = ContextKeyExpr.and(precondition, ContextKeyExpr.deserialize(when)); } else if (when) { @@ -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; } @@ -635,7 +651,7 @@ class UserKeybindings extends Disposable { this._onDidChange.fire(); } }), 50)); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.keybindingsResource))(() => this.reloadConfigurationScheduler.schedule())); + this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.keybindingsResource))(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { diff --git a/src/vs/workbench/services/keybinding/browser/keymapService.ts b/src/vs/workbench/services/keybinding/browser/keymapService.ts index 0dbd5552db7..49326b18b0f 100644 --- a/src/vs/workbench/services/keybinding/browser/keymapService.ts +++ b/src/vs/workbench/services/keybinding/browser/keymapService.ts @@ -474,7 +474,7 @@ class UserKeyboardLayout extends Disposable { } }), 50)); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.keyboardLayoutResource))(() => this.reloadConfigurationScheduler.schedule())); + this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.keyboardLayoutResource))(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { diff --git a/src/vs/workbench/services/keybinding/common/keybindingIO.ts b/src/vs/workbench/services/keybinding/common/keybindingIO.ts index f7c600c0a02..2d4da1d3ff3 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingIO.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingIO.ts @@ -6,7 +6,7 @@ import { SimpleKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -14,7 +14,7 @@ export interface IUserKeybindingItem { parts: (SimpleKeybinding | ScanCodeBinding)[]; command: string | null; commandArgs?: any; - when: ContextKeyExpr | undefined; + when: ContextKeyExpression | undefined; } export class KeybindingIO { 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..2101a705123 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 @@ -6,14 +6,14 @@ import * as assert from 'assert'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/en.darwin'; // 15% import 'vs/workbench/services/keybinding/browser/keyboardLayouts/de.darwin'; import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -import { BrowserKeyboardMapperFactoryBase } from '../../browser/keymapService'; -import { KeymapInfo, IKeymapInfo } from '../../common/keymapInfo'; +import { BrowserKeyboardMapperFactoryBase } from 'vs/workbench/services/keybinding/browser/keymapService'; +import { KeymapInfo, IKeymapInfo } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; 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 924ac2bb3b1..eb501abe939 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'; @@ -51,15 +51,20 @@ import { TestWindowConfiguration, TestTextFileService } from 'vs/workbench/test/ import { ILabelService } from 'vs/platform/label/common/label'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +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; } - } interface Modifiers { @@ -102,12 +107,16 @@ suite('KeybindingsEditing', () => { instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); 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()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); instantiationService.stub(IFileService, fileService); + instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); + instantiationService.stub(IWorkingCopyFileService, instantiationService.createInstance(WorkingCopyFileService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IBackupFileService, new TestBackupFileService()); diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 8bc3fa0db09..74d17900862 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -5,22 +5,23 @@ 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'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'resourceLabelFormatters', @@ -89,19 +90,21 @@ 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; + @IRemotePathService private readonly remotePathService: IRemotePathService + ) { + super(); } findFormatting(resource: URI): ResourceLabelFormatting | undefined { @@ -199,7 +202,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 +229,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 }); } }; } @@ -263,7 +266,10 @@ export class LabelService implements ILabelService { } if (formatting.tildify && !forceNoTildify) { - label = tildify(label, this.environmentService.userHome); + const userHome = this.remotePathService.userHomeSync; + if (userHome) { + label = tildify(label, userHome.fsPath); + } } if (formatting.authorityPrefix && resource.authority) { label = formatting.authorityPrefix + label; @@ -274,7 +280,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..ddecfd5863b 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -4,19 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TestEnvironmentService, TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestRemotePathService } 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', () => { let labelService: LabelService; setup(() => { - labelService = new LabelService(TestEnvironmentService, new TestContextService()); + labelService = new LabelService(TestEnvironmentService, new TestContextService(), new TestRemotePathService(TestEnvironmentService)); }); test('file scheme', function () { 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 db944a5df6a..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 { @@ -52,7 +52,7 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement this.rotatingFilePath = resources.joinPath(rotatingFilePathDirectory, `${id}.1.log`); this._register(fileService.watch(rotatingFilePathDirectory)); - this._register(fileService.onFileChanges(e => { + this._register(fileService.onDidFilesChange(e => { if (e.contains(this.rotatingFilePath)) { this.resettingDelayer.trigger(() => this.resetModel()); } @@ -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/path/common/remotePathService.ts b/src/vs/workbench/services/path/common/remotePathService.ts index b2d63984cd4..a1e875a0e13 100644 --- a/src/vs/workbench/services/path/common/remotePathService.ts +++ b/src/vs/workbench/services/path/common/remotePathService.ts @@ -9,19 +9,35 @@ import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const REMOTE_PATH_SERVICE_ID = 'remotePath'; export const IRemotePathService = createDecorator(REMOTE_PATH_SERVICE_ID); export interface IRemotePathService { + _serviceBrand: undefined; + /** + * The path library to use for the target remote environment. + */ readonly path: Promise; + + /** + * Converts the given path to a file URI in the remote environment. + */ fileURI(path: string): Promise; + /** + * Resolves the user home of the remote environment if defined. + */ readonly userHome: Promise; + + /** + * Provides access to the user home of the remote environment + * if defined. + */ + readonly userHomeSync: URI | undefined; } /** @@ -31,12 +47,15 @@ export class RemotePathService implements IRemotePathService { _serviceBrand: undefined; private _extHostOS: Promise; + private _userHomeSync: URI | undefined; constructor( @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { this._extHostOS = remoteAgentService.getEnvironment().then(remoteEnvironment => { + this._userHomeSync = remoteEnvironment?.userHome; + return remoteEnvironment ? remoteEnvironment.os : platform.OS; }); } @@ -91,9 +110,13 @@ export class RemotePathService implements IRemotePathService { } // local: use the userHome from environment - return URI.from({ scheme: Schemas.file, path: this.environmentService.userHome }); + return this.environmentService.userHome!; }); } + + get userHomeSync(): URI | undefined { + return this._userHomeSync || this.environmentService.userHome; + } } registerSingleton(IRemotePathService, RemotePathService, true); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 1e52b5bb0b2..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; @@ -207,18 +210,18 @@ export class PreferencesService extends Disposable implements IPreferencesServic } const editorInput = this.getActiveSettingsEditorInput() || this.lastOpenedSettingsInput; - const resource = editorInput ? editorInput.master.getResource()! : this.userSettingsResource; + const resource = editorInput ? editorInput.master.resource! : this.userSettingsResource; const target = this.getConfigurationTargetFromSettingsResource(resource); 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,44 +310,30 @@ 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.getResource(); + const editorInputResource = editorInput.master.resource; if (editorInputResource && editorInputResource.fsPath !== resource.fsPath) { 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 14572bc2df7..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'; @@ -67,6 +67,7 @@ export interface ISetting { enumDescriptions?: string[]; enumDescriptionsAreMarkdown?: boolean; tags?: string[]; + disallowSyncIgnore?: boolean; extensionInfo?: IConfigurationExtensionInfo; validator?: (value: any) => string | null; } @@ -154,6 +155,7 @@ export interface ISettingsEditorOptions extends IEditorOptions { target?: ConfigurationTarget; folderUri?: URI; query?: string; + editSetting?: string; } /** @@ -164,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(); @@ -172,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; } @@ -193,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 928d441a336..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'; @@ -75,6 +76,8 @@ export class KeybindingsEditorInput extends EditorInput { searchOptions: IKeybindingsEditorSearchOptions | null = null; + readonly resource = undefined; + constructor(@IInstantiationService instantiationService: IInstantiationService) { super(); this.keybindingsModel = instantiationService.createInstance(KeybindingsEditorModel, OS); @@ -101,8 +104,9 @@ export class SettingsEditor2Input extends EditorInput { static readonly ID: string = 'workbench.input.settings2'; private readonly _settingsModel: Settings2EditorModel; - private resource: URI = URI.from({ - scheme: 'vscode-settings', + + readonly resource: URI = URI.from({ + scheme: Schemas.vscodeSettings, path: `settingseditor` }); @@ -129,8 +133,4 @@ export class SettingsEditor2Input extends EditorInput { resolve(): Promise { return Promise.resolve(this._settingsModel); } - - getResource(): URI { - return this.resource; - } } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 87d06698641..93cfa52a02e 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -24,6 +24,7 @@ import { EditorModel } from 'vs/workbench/common/editor'; import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { withNullAsUndefined, isArray } from 'vs/base/common/types'; import { FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation'; export const nullRange: IRange = { startLineNumber: -1, startColumn: -1, endLineNumber: -1, endColumn: -1 }; export function isNullRange(range: IRange): boolean { return range.startLineNumber === -1 && range.startColumn === -1 && range.endLineNumber === -1 && range.endColumn === -1; } @@ -636,6 +637,7 @@ export class DefaultSettings extends Disposable { enumDescriptions: prop.enumDescriptions || prop.markdownEnumDescriptions, enumDescriptionsAreMarkdown: !prop.enumDescriptions, tags: prop.tags, + disallowSyncIgnore: prop.disallowSyncIgnore, extensionInfo: extensionInfo, deprecationMessage: prop.deprecationMessage, validator: createValidator(prop) @@ -1027,180 +1029,6 @@ class SettingsContentBuilder { } } -export function createValidator(prop: IConfigurationPropertySchema): (value: any) => (string | null) { - // Only for array of string - if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type === 'string') { - const propItems = prop.items; - if (propItems && !isArray(propItems) && propItems.type === 'string') { - const withQuotes = (s: string) => `'` + s + `'`; - - return value => { - if (!value) { - return null; - } - - let message = ''; - - const stringArrayValue = value as string[]; - - if (prop.uniqueItems) { - if (new Set(stringArrayValue).size < stringArrayValue.length) { - message += nls.localize('validations.stringArrayUniqueItems', 'Array has duplicate items'); - message += '\n'; - } - } - - if (prop.minItems && stringArrayValue.length < prop.minItems) { - message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems); - message += '\n'; - } - - if (prop.maxItems && stringArrayValue.length > prop.maxItems) { - message += nls.localize('validations.stringArrayMaxItem', 'Array must have at most {0} items', prop.maxItems); - message += '\n'; - } - - if (typeof propItems.pattern === 'string') { - const patternRegex = new RegExp(propItems.pattern); - stringArrayValue.forEach(v => { - if (!patternRegex.test(v)) { - message += - propItems.patternErrorMessage || - nls.localize( - 'validations.stringArrayItemPattern', - 'Value {0} must match regex {1}.', - withQuotes(v), - withQuotes(propItems.pattern!) - ); - } - }); - } - - const propItemsEnum = propItems.enum; - if (propItemsEnum) { - stringArrayValue.forEach(v => { - if (propItemsEnum.indexOf(v) === -1) { - message += nls.localize( - 'validations.stringArrayItemEnum', - 'Value {0} is not one of {1}', - withQuotes(v), - '[' + propItemsEnum.map(withQuotes).join(', ') + ']' - ); - message += '\n'; - } - }); - } - - return message; - }; - } - } - - return value => { - let exclusiveMax: number | undefined; - let exclusiveMin: number | undefined; - - if (typeof prop.exclusiveMaximum === 'boolean') { - exclusiveMax = prop.exclusiveMaximum ? prop.maximum : undefined; - } else { - exclusiveMax = prop.exclusiveMaximum; - } - - if (typeof prop.exclusiveMinimum === 'boolean') { - exclusiveMin = prop.exclusiveMinimum ? prop.minimum : undefined; - } else { - exclusiveMin = prop.exclusiveMinimum; - } - - let patternRegex: RegExp | undefined; - if (typeof prop.pattern === 'string') { - patternRegex = new RegExp(prop.pattern); - } - - const type: (string | undefined)[] = Array.isArray(prop.type) ? prop.type : [prop.type]; - const canBeType = (t: string) => type.indexOf(t) > -1; - - const isNullable = canBeType('null'); - const isNumeric = (canBeType('number') || canBeType('integer')) && (type.length === 1 || type.length === 2 && isNullable); - const isIntegral = (canBeType('integer')) && (type.length === 1 || type.length === 2 && isNullable); - - type Validator = { enabled: boolean, isValid: (value: T) => boolean; message: string }; - - const numericValidations: Validator[] = isNumeric ? [ - { - enabled: exclusiveMax !== undefined && (prop.maximum === undefined || exclusiveMax <= prop.maximum), - isValid: ((value: number) => value < exclusiveMax!), - message: nls.localize('validations.exclusiveMax', "Value must be strictly less than {0}.", exclusiveMax) - }, - { - enabled: exclusiveMin !== undefined && (prop.minimum === undefined || exclusiveMin >= prop.minimum), - isValid: ((value: number) => value > exclusiveMin!), - message: nls.localize('validations.exclusiveMin', "Value must be strictly greater than {0}.", exclusiveMin) - }, - - { - enabled: prop.maximum !== undefined && (exclusiveMax === undefined || exclusiveMax > prop.maximum), - isValid: ((value: number) => value <= prop.maximum!), - message: nls.localize('validations.max', "Value must be less than or equal to {0}.", prop.maximum) - }, - { - enabled: prop.minimum !== undefined && (exclusiveMin === undefined || exclusiveMin < prop.minimum), - isValid: ((value: number) => value >= prop.minimum!), - message: nls.localize('validations.min', "Value must be greater than or equal to {0}.", prop.minimum) - }, - { - enabled: prop.multipleOf !== undefined, - isValid: ((value: number) => value % prop.multipleOf! === 0), - message: nls.localize('validations.multipleOf', "Value must be a multiple of {0}.", prop.multipleOf) - }, - { - enabled: isIntegral, - isValid: ((value: number) => value % 1 === 0), - message: nls.localize('validations.expectedInteger', "Value must be an integer.") - }, - ].filter(validation => validation.enabled) : []; - - const stringValidations: Validator[] = [ - { - enabled: prop.maxLength !== undefined, - isValid: ((value: { length: number; }) => value.length <= prop.maxLength!), - message: nls.localize('validations.maxLength', "Value must be {0} or fewer characters long.", prop.maxLength) - }, - { - enabled: prop.minLength !== undefined, - isValid: ((value: { length: number; }) => value.length >= prop.minLength!), - message: nls.localize('validations.minLength', "Value must be {0} or more characters long.", prop.minLength) - }, - { - enabled: patternRegex !== undefined, - isValid: ((value: string) => patternRegex!.test(value)), - message: prop.patternErrorMessage || nls.localize('validations.regex', "Value must match regex `{0}`.", prop.pattern) - }, - ].filter(validation => validation.enabled); - - if (prop.type === 'string' && stringValidations.length === 0) { return null; } - if (isNullable && value === '') { return ''; } - - const errors: string[] = []; - - if (isNumeric) { - if (value === '' || isNaN(+value)) { - errors.push(nls.localize('validations.expectedNumeric', "Value must be a number.")); - } else { - errors.push(...numericValidations.filter(validator => !validator.isValid(+value)).map(validator => validator.message)); - } - } - - if (prop.type === 'string') { - errors.push(...stringValidations.filter(validator => !validator.isValid('' + value)).map(validator => validator.message)); - } - if (errors.length) { - return prop.errorMessage ? [prop.errorMessage, ...errors].join(' ') : errors.join(' '); - } - return ''; - }; -} - class RawSettingsContentBuilder extends SettingsContentBuilder { constructor(private indent: string = '\t') { diff --git a/src/vs/workbench/services/preferences/common/preferencesValidation.ts b/src/vs/workbench/services/preferences/common/preferencesValidation.ts new file mode 100644 index 00000000000..9a1080649a6 --- /dev/null +++ b/src/vs/workbench/services/preferences/common/preferencesValidation.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 { JSONSchemaType } from 'vs/base/common/jsonSchema'; +import { isArray } from 'vs/base/common/types'; +import * as nls from 'vs/nls'; +import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; + +type Validator = { enabled: boolean, isValid: (value: T) => boolean; message: string }; + +function canBeType(propTypes: (string | undefined)[], ...types: JSONSchemaType[]): boolean { + return types.some(t => propTypes.includes(t)); +} + +export function createValidator(prop: IConfigurationPropertySchema): (value: any) => (string | null) { + const type: (string | undefined)[] = Array.isArray(prop.type) ? prop.type : [prop.type]; + const isNullable = canBeType(type, 'null'); + const isNumeric = (canBeType(type, 'number') || canBeType(type, 'integer')) && (type.length === 1 || type.length === 2 && isNullable); + + const numericValidations = getNumericValidators(prop); + const stringValidations = getStringValidators(prop); + const stringArrayValidator = getArrayOfStringValidator(prop); + + return value => { + if (prop.type === 'string' && stringValidations.length === 0) { return null; } + if (isNullable && value === '') { return ''; } + + const errors: string[] = []; + if (stringArrayValidator) { + const err = stringArrayValidator(value); + if (err) { + errors.push(err); + } + } + + if (isNumeric) { + if (value === '' || isNaN(+value)) { + errors.push(nls.localize('validations.expectedNumeric', "Value must be a number.")); + } else { + errors.push(...numericValidations.filter(validator => !validator.isValid(+value)).map(validator => validator.message)); + } + } + + if (prop.type === 'string') { + errors.push(...stringValidations.filter(validator => !validator.isValid('' + value)).map(validator => validator.message)); + } + + if (errors.length) { + return prop.errorMessage ? [prop.errorMessage, ...errors].join(' ') : errors.join(' '); + } + + return ''; + }; +} + +export function getInvalidTypeError(value: any, type: undefined | string | string[]): string | undefined { + let typeArr = Array.isArray(type) ? type : [type]; + const isNullable = canBeType(typeArr, 'null'); + if (canBeType(typeArr, 'number', 'integer') && (typeArr.length === 1 || typeArr.length === 2 && isNullable)) { + if (value === '' || isNaN(+value)) { + return nls.localize('validations.expectedNumeric', "Value must be a number."); + } + } + + const valueType = typeof value; + if ( + (valueType === 'boolean' && !canBeType(typeArr, 'boolean')) || + (valueType === 'object' && !canBeType(typeArr, 'object', 'null', 'array')) || + (valueType === 'string' && !canBeType(typeArr, 'string', 'number', 'integer')) || + (typeof parseFloat(value) === 'number' && !isNaN(parseFloat(value)) && !canBeType(typeArr, 'number', 'integer')) || + (Array.isArray(value) && !canBeType(typeArr, 'array')) + ) { + if (typeof type !== 'undefined') { + return nls.localize('invalidTypeError', "Setting has an invalid type, expected {0}. Fix in JSON.", JSON.stringify(type)); + } + } + + return; +} + +function getStringValidators(prop: IConfigurationPropertySchema) { + let patternRegex: RegExp | undefined; + if (typeof prop.pattern === 'string') { + patternRegex = new RegExp(prop.pattern); + } + return [ + { + enabled: prop.maxLength !== undefined, + isValid: ((value: { length: number; }) => value.length <= prop.maxLength!), + message: nls.localize('validations.maxLength', "Value must be {0} or fewer characters long.", prop.maxLength) + }, + { + enabled: prop.minLength !== undefined, + isValid: ((value: { length: number; }) => value.length >= prop.minLength!), + message: nls.localize('validations.minLength', "Value must be {0} or more characters long.", prop.minLength) + }, + { + enabled: patternRegex !== undefined, + isValid: ((value: string) => patternRegex!.test(value)), + message: prop.patternErrorMessage || nls.localize('validations.regex', "Value must match regex `{0}`.", prop.pattern) + }, + ].filter(validation => validation.enabled); +} + +function getNumericValidators(prop: IConfigurationPropertySchema): Validator[] { + const type: (string | undefined)[] = Array.isArray(prop.type) ? prop.type : [prop.type]; + + const isNullable = canBeType(type, 'null'); + const isIntegral = (canBeType(type, 'integer')) && (type.length === 1 || type.length === 2 && isNullable); + const isNumeric = canBeType(type, 'number', 'integer') && (type.length === 1 || type.length === 2 && isNullable); + if (!isNumeric) { + return []; + } + + let exclusiveMax: number | undefined; + let exclusiveMin: number | undefined; + + if (typeof prop.exclusiveMaximum === 'boolean') { + exclusiveMax = prop.exclusiveMaximum ? prop.maximum : undefined; + } else { + exclusiveMax = prop.exclusiveMaximum; + } + + if (typeof prop.exclusiveMinimum === 'boolean') { + exclusiveMin = prop.exclusiveMinimum ? prop.minimum : undefined; + } else { + exclusiveMin = prop.exclusiveMinimum; + } + + return [ + { + enabled: exclusiveMax !== undefined && (prop.maximum === undefined || exclusiveMax <= prop.maximum), + isValid: ((value: number) => value < exclusiveMax!), + message: nls.localize('validations.exclusiveMax', "Value must be strictly less than {0}.", exclusiveMax) + }, + { + enabled: exclusiveMin !== undefined && (prop.minimum === undefined || exclusiveMin >= prop.minimum), + isValid: ((value: number) => value > exclusiveMin!), + message: nls.localize('validations.exclusiveMin', "Value must be strictly greater than {0}.", exclusiveMin) + }, + + { + enabled: prop.maximum !== undefined && (exclusiveMax === undefined || exclusiveMax > prop.maximum), + isValid: ((value: number) => value <= prop.maximum!), + message: nls.localize('validations.max', "Value must be less than or equal to {0}.", prop.maximum) + }, + { + enabled: prop.minimum !== undefined && (exclusiveMin === undefined || exclusiveMin < prop.minimum), + isValid: ((value: number) => value >= prop.minimum!), + message: nls.localize('validations.min', "Value must be greater than or equal to {0}.", prop.minimum) + }, + { + enabled: prop.multipleOf !== undefined, + isValid: ((value: number) => value % prop.multipleOf! === 0), + message: nls.localize('validations.multipleOf', "Value must be a multiple of {0}.", prop.multipleOf) + }, + { + enabled: isIntegral, + isValid: ((value: number) => value % 1 === 0), + message: nls.localize('validations.expectedInteger', "Value must be an integer.") + }, + ].filter(validation => validation.enabled); +} + +function getArrayOfStringValidator(prop: IConfigurationPropertySchema): ((value: any) => (string | null)) | null { + if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type === 'string') { + const propItems = prop.items; + if (propItems && !isArray(propItems) && propItems.type === 'string') { + const withQuotes = (s: string) => `'` + s + `'`; + return value => { + if (!value) { + return null; + } + + let message = ''; + + const stringArrayValue = value as string[]; + + if (prop.uniqueItems) { + if (new Set(stringArrayValue).size < stringArrayValue.length) { + message += nls.localize('validations.stringArrayUniqueItems', 'Array has duplicate items'); + message += '\n'; + } + } + + if (prop.minItems && stringArrayValue.length < prop.minItems) { + message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems); + message += '\n'; + } + + if (prop.maxItems && stringArrayValue.length > prop.maxItems) { + message += nls.localize('validations.stringArrayMaxItem', 'Array must have at most {0} items', prop.maxItems); + message += '\n'; + } + + if (typeof propItems.pattern === 'string') { + const patternRegex = new RegExp(propItems.pattern); + stringArrayValue.forEach(v => { + if (!patternRegex.test(v)) { + message += + propItems.patternErrorMessage || + nls.localize( + 'validations.stringArrayItemPattern', + 'Value {0} must match regex {1}.', + withQuotes(v), + withQuotes(propItems.pattern!) + ); + } + }); + } + + const propItemsEnum = propItems.enum; + if (propItemsEnum) { + stringArrayValue.forEach(v => { + if (propItemsEnum.indexOf(v) === -1) { + message += nls.localize( + 'validations.stringArrayItemEnum', + 'Value {0} is not one of {1}', + withQuotes(v), + '[' + propItemsEnum.map(withQuotes).join(', ') + ']' + ); + message += '\n'; + } + }); + } + + return message; + }; + } + } + + return null; +} + + diff --git a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts index 65673979df4..a9ff80cc2b9 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { createValidator } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation'; suite('Preferences Model test', () => { diff --git a/src/vs/workbench/services/progress/browser/media/progressService.css b/src/vs/workbench/services/progress/browser/media/progressService.css index 6b73a2f3a4e..a1e92a76439 100644 --- a/src/vs/workbench/services/progress/browser/media/progressService.css +++ b/src/vs/workbench/services/progress/browser/media/progressService.css @@ -3,14 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.progress { - padding-left: 5px; -} - -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.progress .spinner-container { - padding-right: 5px; -} - .monaco-workbench .progress-badge > .badge-content::before { mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); -webkit-mask: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4="); diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index ff1da4bab76..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 { @@ -45,7 +46,7 @@ export class ProgressBarIndicator extends Disposable implements IProgressIndicat }; } - async showWhile(promise: Promise, delay?: number): Promise { + async showWhile(promise: Promise, delay?: number): Promise { try { this.progressbar.infinite().show(delay); @@ -92,7 +93,7 @@ export class EditorProgressIndicator extends ProgressBarIndicator { return super.show(infiniteOrTotal, delay); } - async showWhile(promise: Promise, delay?: number): Promise { + async showWhile(promise: Promise, delay?: number): Promise { // No editor open: ignore any progress reporting if (this.group.isEmpty) { @@ -125,7 +126,7 @@ namespace ProgressIndicatorState { readonly type = Type.While; constructor( - readonly whilePromise: Promise, + readonly whilePromise: Promise, readonly whileStart: number, readonly whileDelay: number, ) { } @@ -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 { @@ -311,7 +318,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr }; } - async showWhile(promise: Promise, delay?: number): Promise { + async showWhile(promise: Promise, delay?: number): Promise { // Join with existing running promise to ensure progress is accurate if (this.progressState.type === ProgressIndicatorState.Type.While) { diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 4a5831b5667..f278eb996f2 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -6,15 +6,15 @@ import 'vs/css!./media/progressService'; import { localize } from 'vs/nls'; -import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable } 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, INotificationActions } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity, INotificationHandle } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; @@ -24,17 +24,18 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; 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, @@ -56,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}`); } @@ -63,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: @@ -77,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())]; @@ -84,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 @@ -92,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); @@ -103,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; @@ -135,18 +150,116 @@ 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; } } - private withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: (choice?: number) => void): P { - const toDispose = new DisposableStore(); + private withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress) => P, onDidCancel?: (choice?: number) => void): P { - const createNotification = (message: string, increment?: number): INotificationHandle => { + const progressStateModel = new class extends Disposable { + + private readonly _onDidReport = this._register(new Emitter()); + readonly onDidReport = this._onDidReport.event; + + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; + + private _step: IProgressStep | undefined = undefined; + get step() { return this._step; } + + private _done = false; + get done() { return this._done; } + + readonly promise: P; + + constructor() { + super(); + + this.promise = callback(this); + + this.promise.finally(() => { + this.dispose(); + }); + } + + report(step: IProgressStep): void { + this._step = step; + + this._onDidReport.fire(step); + } + + cancel(choice?: number): void { + onDidCancel?.(choice); + + this.dispose(); + } + + dispose(): void { + this._done = true; + this._onDispose.fire(); + + super.dispose(); + } + }; + + const createWindowProgress = () => { + + // Create a promise that we can resolve as needed + // when the outside calls dispose on us + let promiseResolve: () => void; + const promise = new Promise(resolve => promiseResolve = resolve); + + this.withWindowProgress({ + location: ProgressLocation.Window, + title: options.title ? parseLinkedText(options.title).toString() : undefined, // convert markdown links => string + command: 'notifications.showList' + }, progress => { + + function reportProgress(step: IProgressStep) { + if (step.message) { + progress.report({ + message: parseLinkedText(step.message).toString() // convert markdown links => string + }); + } + } + + // Apply any progress that was made already + if (progressStateModel.step) { + reportProgress(progressStateModel.step); + } + + // Continue to report progress as it happens + const onDidReportListener = progressStateModel.onDidReport(step => reportProgress(step)); + promise.finally(() => onDidReportListener.dispose()); + + // When the progress model gets disposed, we are done as well + Event.once(progressStateModel.onDispose)(() => promiseResolve()); + + return promise; + }); + + // Dispose means completing our promise + return toDisposable(() => promiseResolve()); + }; + + const createNotification = (message: string, silent: boolean, increment?: number): INotificationHandle => { + const notificationDisposables = new DisposableStore(); const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : []; const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : []; @@ -158,16 +271,11 @@ export class ProgressService extends Disposable implements IProgressService { super(`progress.button.${button}`, button, undefined, true); } - run(): Promise { - if (typeof onDidCancel === 'function') { - onDidCancel(index); - } - - return Promise.resolve(undefined); + async run(): Promise { + progressStateModel.cancel(index); } }; - - toDispose.add(buttonAction); + notificationDisposables.add(buttonAction); primaryActions.push(buttonAction); }); @@ -179,34 +287,47 @@ export class ProgressService extends Disposable implements IProgressService { super('progress.cancel', localize('cancel', "Cancel"), undefined, true); } - run(): Promise { - if (typeof onDidCancel === 'function') { - onDidCancel(); - } - - return Promise.resolve(undefined); + async run(): Promise { + progressStateModel.cancel(); } }; - toDispose.add(cancelAction); + notificationDisposables.add(cancelAction); primaryActions.push(cancelAction); } - const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; - const handle = this.notificationService.notify({ + const notification = this.notificationService.notify({ severity: Severity.Info, message, source: options.source, - actions + actions: { primary: primaryActions, secondary: secondaryActions }, + progress: typeof increment === 'number' && increment >= 0 ? { total: 100, worked: increment } : { infinite: true }, + silent }); - updateProgress(handle, increment); + // Switch to window based progress once the notification + // changes visibility to hidden and is still ongoing. + // Remove that window based progress once the notification + // shows again. + let windowProgressDisposable: IDisposable | undefined = undefined; + const onVisibilityChange = (visible: boolean) => { + // Clear any previous running window progress + dispose(windowProgressDisposable); - Event.once(handle.onDidClose)(() => { - toDispose.dispose(); - }); + // Create new window progress if notification got hidden + if (!visible && !progressStateModel.done) { + windowProgressDisposable = createWindowProgress(); + } + }; + notificationDisposables.add(notification.onDidChangeVisibility(onVisibilityChange)); + if (silent) { + onVisibilityChange(false); + } - return handle; + // Clear upon dispose + Event.once(notification.onDidClose)(() => notificationDisposables.dispose()); + + return notification; }; const updateProgress = (notification: INotificationHandle, increment?: number): void => { @@ -218,60 +339,68 @@ export class ProgressService extends Disposable implements IProgressService { } }; - let handle: INotificationHandle | undefined; - let handleSoon: any | undefined; - + let notificationHandle: INotificationHandle | undefined; + let notificationTimeout: any | undefined; let titleAndMessage: string | undefined; // hoisted to make sure a delayed notification shows the most recent message - const updateNotification = (message?: string, increment?: number): void => { + const updateNotification = (step?: IProgressStep): void => { // full message (inital or update) - if (message && options.title) { - titleAndMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) + if (step?.message && options.title) { + titleAndMessage = `${options.title}: ${step.message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) } else { - titleAndMessage = options.title || message; + titleAndMessage = options.title || step?.message; } - if (!handle && titleAndMessage) { + if (!notificationHandle && titleAndMessage) { + // create notification now or after a delay if (typeof options.delay === 'number' && options.delay > 0) { - if (typeof handleSoon !== 'number') { - handleSoon = setTimeout(() => handle = createNotification(titleAndMessage!, increment), options.delay); + if (typeof notificationTimeout !== 'number') { + notificationTimeout = setTimeout(() => notificationHandle = createNotification(titleAndMessage!, !!options.silent, step?.increment), options.delay); } } else { - handle = createNotification(titleAndMessage, increment); + notificationHandle = createNotification(titleAndMessage, !!options.silent, step?.increment); } } - if (handle) { + if (notificationHandle) { if (titleAndMessage) { - handle.updateMessage(titleAndMessage); + notificationHandle.updateMessage(titleAndMessage); } - if (typeof increment === 'number') { - updateProgress(handle, increment); + + if (typeof step?.increment === 'number') { + updateProgress(notificationHandle, step.increment); } } }; // Show initially - updateNotification(); + updateNotification(progressStateModel.step); + const listener = progressStateModel.onDidReport(step => updateNotification(step)); + Event.once(progressStateModel.onDispose)(() => listener.dispose()); - // Update based on progress - const promise = callback({ - report: progress => { - updateNotification(progress.message, progress.increment); + // Clean up eventually + (async () => { + try { + + // with a delay we only wait for the finish of the promise + if (typeof options.delay === 'number' && options.delay > 0) { + await progressStateModel.promise; + } + + // without a delay we show the notification for at least 800ms + // to reduce the chance of the notification flashing up and hiding + else { + await Promise.all([timeout(800), progressStateModel.promise]); + } + } finally { + clearTimeout(notificationTimeout); + notificationHandle?.close(); } - }); + })(); - // Show progress for at least 800ms and then hide once done or canceled - Promise.all([timeout(800), promise]).finally(() => { - clearTimeout(handleSoon); - if (handle) { - handle.close(); - } - }); - - return promise; + return progressStateModel.promise; } private withViewletProgress

, R = unknown>(viewletId: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { @@ -279,18 +408,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 = { @@ -306,13 +455,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..fcdad25645e --- /dev/null +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.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 { 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 { 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( + @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.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/electron-browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts index ff2fc30d984..0fecb23ec17 100644 --- a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import product from 'vs/platform/product/common/product'; @@ -13,6 +11,7 @@ import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/ import { ISignService } from 'vs/platform/sign/common/sign'; import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { ILogService } from 'vs/platform/log/common/log'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { @@ -20,16 +19,16 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR private readonly _connection: IRemoteAgentConnection | null = null; - constructor({ remoteAuthority }: IWindowConfiguration, - @IEnvironmentService environmentService: IEnvironmentService, + constructor( + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService signService: ISignService, @ILogService logService: ILogService ) { super(environmentService); this.socketFactory = nodeSocketFactory; - if (remoteAuthority) { - this._connection = this._register(new RemoteAgentConnection(remoteAuthority, product.commit, nodeSocketFactory, remoteAuthorityResolverService, signService, logService)); + if (environmentService.configuration.remoteAuthority) { + this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority, product.commit, nodeSocketFactory, remoteAuthorityResolverService, signService, logService)); } } 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 99ba52ad15f..b857380aa3a 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; @@ -198,6 +203,12 @@ export interface ISearchCompleteStats { export interface ISearchComplete extends ISearchCompleteStats { results: IFileMatch[]; + exit?: SearchCompletionExitCode +} + +export const enum SearchCompletionExitCode { + Normal, + NewSearchStarted } export interface ITextSearchStats { @@ -334,9 +345,10 @@ export interface ISearchConfigurationProperties { collapseResults: 'auto' | 'alwaysCollapse' | 'alwaysExpand'; searchOnType: boolean; searchOnTypeDebouncePeriod: number; - enableSearchEditorPreview: boolean; - searchEditorPreview: { doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide' }; - searchEditorPreviewForceAbsolutePaths: boolean; + searchEditor: { + doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide', + experimental: { reusePriorSearchConfiguration: boolean } + }; sortOrder: SearchSortOrder; } @@ -367,6 +379,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; @@ -433,9 +453,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 { @@ -482,6 +512,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/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 23fe16f149a..53cfa0b9f15 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -12,8 +12,8 @@ import { URI as uri } from 'vs/base/common/uri'; import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IDebugParams } from 'vs/platform/environment/common/environment'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IDebugParams, IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { parseSearchPort, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { FileMatch, IFileMatch, IFileQuery, IProgressMessage, IRawSearchService, ISearchComplete, ISearchConfiguration, ISearchProgressItem, ISearchResultProvider, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITextQuery, ISearchService, isFileMatch } from 'vs/workbench/services/search/common/search'; @@ -25,7 +25,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { parseSearchPort } from 'vs/platform/environment/node/environmentService'; export class LocalSearchService extends SearchService { constructor( @@ -35,12 +34,11 @@ export class LocalSearchService extends SearchService { @ILogService logService: ILogService, @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService, - @IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService, + @IEnvironmentService readonly environmentService: INativeEnvironmentService, @IInstantiationService readonly instantiationService: IInstantiationService ) { super(modelService, editorService, telemetryService, logService, extensionService, fileService); - this.diskSearch = instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, parseSearchPort(environmentService.args, environmentService.isBuilt)); } } 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 91519f20cbd..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?: any[]; + 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..6104f63dc1c 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, @@ -34,11 +35,11 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.enableTelemetry) { + if (!environmentService.isExtensionDevelopment && !environmentService.disableTelemetry && !!productService.enableTelemetry) { 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/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index c671d0dc643..83d5fba161a 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; -import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IResourceEncodings, IResourceEncoding, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; @@ -24,7 +24,7 @@ export class BrowserTextFileService extends AbstractTextFileService { } protected onBeforeShutdown(reason: ShutdownReason): boolean { - if (this.files.getAll().some(model => model.hasState(ModelState.PENDING_SAVE))) { + if (this.files.models.some(model => model.hasState(TextFileEditorModelState.PENDING_SAVE))) { console.warn('Unload prevented: pending file saves'); return true; // files are pending to be saved: veto diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 1f6c9878fa5..ef6a97ac6ec 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -5,8 +5,8 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { Emitter, AsyncEmitter } from 'vs/base/common/event'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions, ITextFileEditorModelManager, ISaveParticipant } from 'vs/workbench/services/textfile/common/textfiles'; +import { AsyncEmitter } from 'vs/base/common/event'; +import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, ITextFileSaveOptions, ITextFileEditorModelManager, TextFileCreateEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; @@ -16,11 +16,10 @@ import { IUntitledTextEditorService, IUntitledTextEditorModelManager } from 'vs/ import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { isEqualOrParent, isEqual, joinPath, dirname, basename, toLocalResource } from 'vs/base/common/resources'; +import { isEqual, joinPath, dirname, basename, toLocalResource } from 'vs/base/common/resources'; import { IDialogService, IFileDialogService, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; import { VSBuffer } from 'vs/base/common/buffer'; import { ITextSnapshot, ITextModel } from 'vs/editor/common/model'; @@ -31,12 +30,10 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { coalesce } from 'vs/base/common/arrays'; import { suggestFilename } from 'vs/base/common/mime'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { isValidBasename } from 'vs/base/common/extpath'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -47,11 +44,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region events - private _onWillRunOperation = this._register(new AsyncEmitter()); - readonly onWillRunOperation = this._onWillRunOperation.event; - - private _onDidRunOperation = this._register(new Emitter()); - readonly onDidRunOperation = this._onDidRunOperation.event; + private _onDidCreateTextFile = this._register(new AsyncEmitter()); + readonly onDidCreateTextFile = this._onDidCreateTextFile.event; //#endregion @@ -59,18 +53,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex readonly untitled: IUntitledTextEditorModelManager = this.untitledTextEditorService; - saveErrorHandler = (() => { - const notificationService = this.notificationService; - - return { - onSaveError(error: Error, model: ITextFileEditorModel): void { - notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", model.name, toErrorMessage(error, false))); - } - }; - })(); - - saveParticipant: ISaveParticipant | undefined = undefined; - abstract get encoding(): IResourceEncodings; constructor( @@ -86,8 +68,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService, @ITextModelService private readonly textModelService: ITextModelService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @INotificationService private readonly notificationService: INotificationService, - @IRemotePathService private readonly remotePathService: IRemotePathService + @IRemotePathService private readonly remotePathService: IRemotePathService, + @IWorkingCopyFileService private readonly workingCopyFileService: IWorkingCopyFileService ) { super(); @@ -100,7 +82,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex this.lifecycleService.onShutdown(this.dispose, this); } - //#region text file read / write + //#region text file read / write / create async read(resource: URI, options?: IReadTextFileOptions): Promise { const content = await this.fileService.readFile(resource, options); @@ -156,19 +138,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } } - async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { - return this.fileService.writeFile(resource, toBufferOrReadable(value), options); - } - - //#endregion - - //#region text file IO primitives (create, move, copy, delete) - async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { - // before event - await this._onWillRunOperation.fireAsync({ operation: FileOperation.CREATE, target: resource }, CancellationToken.None); + // file operation participation + await this.workingCopyFileService.runFileOperationParticipants(resource, undefined, FileOperation.CREATE); + // create file on disk const stat = await this.doCreate(resource, value, options); // If we had an existing model for the given resource, load @@ -181,7 +156,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // after event - this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.CREATE, resource)); + await this._onDidCreateTextFile.fireAsync({ resource }, CancellationToken.None); return stat; } @@ -190,127 +165,13 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.fileService.createFile(resource, toBufferOrReadable(value), options); } - async move(source: URI, target: URI, overwrite?: boolean): Promise { - return this.moveOrCopy(source, target, true, overwrite); - } - - async copy(source: URI, target: URI, overwrite?: boolean): Promise { - return this.moveOrCopy(source, target, false, overwrite); - } - - private async moveOrCopy(source: URI, target: URI, move: boolean, overwrite?: boolean): Promise { - - // before event - await this._onWillRunOperation.fireAsync({ operation: move ? FileOperation.MOVE : FileOperation.COPY, target, source }, CancellationToken.None); - - // find all models that related to either source or target (can be many if resource is a folder) - const sourceModels: ITextFileEditorModel[] = []; - const targetModels: ITextFileEditorModel[] = []; - for (const model of this.getFileModels()) { - const resource = model.resource; - - if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { - targetModels.push(model); - } - - if (isEqualOrParent(resource, source)) { - sourceModels.push(model); - } - } - - // remember each source model to load again after move is done - // with optional content to restore if it was dirty - type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot; encoding?: string; mode?: string }; - const modelsToRestore: ModelToRestore[] = []; - for (const sourceModel of sourceModels) { - const sourceModelResource = sourceModel.resource; - - // If the source is the actual model, just use target as new resource - let modelToRestoreResource: URI; - if (isEqual(sourceModelResource, source)) { - modelToRestoreResource = target; - } - - // Otherwise a parent folder of the source is being moved, so we need - // to compute the target resource based on that - else { - modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); - } - - const modelToRestore: ModelToRestore = { resource: modelToRestoreResource, encoding: sourceModel.getEncoding() }; - if (sourceModel.isDirty()) { - modelToRestore.snapshot = sourceModel.createSnapshot(); - } - - modelsToRestore.push(modelToRestore); - } - - // handle dirty models depending on the operation: - // - move: revert both source and target (if any) - // - copy: revert target (if any) - const dirtyModelsToRevert = (move ? [...sourceModels, ...targetModels] : [...targetModels]).filter(model => model.isDirty()); - await this.doRevertFiles(dirtyModelsToRevert.map(dirtyModel => dirtyModel.resource), { soft: true }); - - // now we can rename the source to target via file operation - let stat: IFileStatWithMetadata; - try { - if (move) { - stat = await this.fileService.move(source, target, overwrite); - } else { - stat = await this.fileService.copy(source, target, overwrite); - } - } catch (error) { - - // in case of any error, ensure to set dirty flag back - dirtyModelsToRevert.forEach(dirtyModel => dirtyModel.setDirty(true)); - - throw error; - } - - // finally, restore models that we had loaded previously - await Promise.all(modelsToRestore.map(async modelToRestore => { - - // restore the model, forcing a reload. this is important because - // we know the file has changed on disk after the move and the - // model might have still existed with the previous state. this - // ensures we are not tracking a stale state. - const restoredModel = await this.files.resolve(modelToRestore.resource, { reload: { async: false }, encoding: modelToRestore.encoding, mode: modelToRestore.mode }); - - // restore previous dirty content if any and ensure to mark - // the model as dirty - if (modelToRestore.snapshot && restoredModel.isResolved()) { - this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); - - restoredModel.setDirty(true); - } - })); - - // after event - this._onDidRunOperation.fire(new FileOperationDidRunEvent(move ? FileOperation.MOVE : FileOperation.COPY, target, source)); - - return stat; - } - - async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - - // before event - await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None); - - // Check for any existing dirty file model for the resource - // and do a soft revert before deleting to be able to close - // any opened editor with these files - const dirtyFiles = this.getDirtyFileModels().map(dirtyFileModel => dirtyFileModel.resource).filter(dirty => isEqualOrParent(dirty, resource)); - await this.doRevertFiles(dirtyFiles, { soft: true }); - - // Now actually delete from disk - await this.fileService.del(resource, options); - - // after event - this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.DELETE, resource)); + async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { + return this.fileService.writeFile(resource, toBufferOrReadable(value), options); } //#endregion + //#region save async save(resource: URI, options?: ITextFileSaveOptions): Promise { @@ -342,29 +203,13 @@ export abstract class AbstractTextFileService extends Disposable implements ITex else { const model = this.files.get(resource); if (model) { - - // Save with options - await model.save(options); - - return !model.isDirty() ? resource : undefined; + return await model.save(options) ? resource : undefined; } } return undefined; } - private getFileModels(resources?: URI[]): ITextFileEditorModel[] { - if (Array.isArray(resources)) { - return coalesce(resources.map(resource => this.files.get(resource))); - } - - return this.files.getAll(); - } - - private getDirtyFileModels(resources?: URI[]): ITextFileEditorModel[] { - return this.getFileModels(resources).filter(model => model.isDirty()); - } - async saveAs(source: URI, target?: URI, options?: ITextFileSaveOptions): Promise { // Get to target resource @@ -532,9 +377,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // save model - await targetModel.save(options); - - return true; + return await targetModel.save(options); } private async confirmOverwrite(resource: URI): Promise { @@ -599,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) { @@ -607,39 +450,15 @@ export abstract class AbstractTextFileService extends Disposable implements ITex if (model) { return model.revert(options); } - - return false; } // File - return !(await this.doRevertFiles([resource], options)).results.some(result => result.error); - } - - private async doRevertFiles(resources: URI[], options?: IRevertOptions): Promise { - const fileModels = options?.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); - - const mapResourceToResult = new ResourceMap(); - fileModels.forEach(fileModel => { - mapResourceToResult.set(fileModel.resource, { - source: fileModel.resource - }); - }); - - await Promise.all(fileModels.map(async model => { - - // Revert through model - await model.revert(options); - - // If model is still dirty, mark the resulting operation as error - if (model.isDirty()) { - const result = mapResourceToResult.get(model.resource); - if (result) { - result.error = true; - } + else { + const model = this.files.get(resource); + if (model && (model.isDirty() || options?.force)) { + return model.revert(options); } - })); - - return { results: mapResourceToResult.values() }; + } } //#endregion diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 6186973a5b8..17eadf382ca 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; -import { ITextFileService, ModelState, ITextFileEditorModel, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileLoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; @@ -43,7 +43,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidLoad = this._register(new Emitter()); + private readonly _onDidLoad = this._register(new Emitter()); readonly onDidLoad = this._onDidLoad.event; private readonly _onDidChangeDirty = this._register(new Emitter()); @@ -110,11 +110,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private registerListeners(): void { - this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); this._register(this.filesConfigurationService.onFilesAssociationChange(e => this.onFilesAssociationChange())); } - private async onFileChanges(e: FileChangesEvent): Promise { + private async onDidFilesChange(e: FileChangesEvent): Promise { let fileEventImpactsModel = false; let newInOrphanModeGuess: boolean | undefined; @@ -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,22 +240,27 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (wasDirty) { this._onDidChangeDirty.fire(); } - - return true; } //#endregion //#region Load - async load(options?: ILoadOptions): Promise { - this.logService.trace('[text file model] load() - enter', this.resource.toString()); + async load(options?: ITextFileLoadOptions): Promise { + this.logService.trace('[text file model] load() - enter', this.resource.toString(true)); + + // Return early if we are disposed + if (this.isDisposed()) { + this.logService.trace('[text file model] load() - exit - without loading because model is disposed', this.resource.toString(true)); + + return this; + } // It is very important to not reload the model when the model is dirty. // We also only want to reload the model from the disk if no save is pending // to avoid data loss. if (this.dirty || this.saveSequentializer.hasPending()) { - this.logService.trace('[text file model] load() - exit - without loading because model is dirty or being saved', this.resource.toString()); + this.logService.trace('[text file model] load() - exit - without loading because model is dirty or being saved', this.resource.toString(true)); return this; } @@ -281,7 +286,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.loadFromFile(options); } - private async loadFromBackup(backup: IResolvedBackup, options?: ILoadOptions): Promise { + private async loadFromBackup(backup: IResolvedBackup, options?: ITextFileLoadOptions): Promise { // Load with backup this.loadFromContent({ @@ -303,7 +308,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this; } - private async loadFromFile(options?: ILoadOptions): Promise { + private async loadFromFile(options?: ITextFileLoadOptions): Promise { const forceReadFromDisk = options?.forceReadFromDisk; const allowBinary = this.isResolved() /* always allow if we resolved previously */ || options?.allowBinary; @@ -358,8 +363,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private loadFromContent(content: ITextFileStreamContent, options?: ILoadOptions, fromBackup?: boolean): TextFileEditorModel { - this.logService.trace('[text file model] load() - resolved content', this.resource.toString()); + private loadFromContent(content: ITextFileStreamContent, options?: ITextFileLoadOptions, fromBackup?: boolean): TextFileEditorModel { + this.logService.trace('[text file model] loadFromContent() - enter', this.resource.toString(true)); + + // Return early if we are disposed + if (this.isDisposed()) { + this.logService.trace('[text file model] loadFromContent() - exit - because model is disposed', this.resource.toString(true)); + + return this; + } // Update our resolved disk stat model this.updateLastResolvedFileStat({ @@ -395,14 +407,17 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.doCreateTextModel(content.resource, content.value, !!fromBackup); } + // Ensure we track the latest saved version ID + this.updateSavedVersionId(); + // Emit as event - this._onDidLoad.fire(options?.reason ?? LoadReason.OTHER); + this._onDidLoad.fire(options?.reason ?? TextFileLoadReason.OTHER); return this; } private doCreateTextModel(resource: URI, value: ITextBufferFactory, fromBackup: boolean): void { - this.logService.trace('[text file model] load() - created text editor model', this.resource.toString()); + this.logService.trace('[text file model] doCreateTextModel()', this.resource.toString(true)); // Create model const textModel = this.createTextEditorModel(value, resource, this.preferredMode); @@ -417,7 +432,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private doUpdateTextModel(value: ITextBufferFactory): void { - this.logService.trace('[text file model] load() - updated text editor model', this.resource.toString()); + this.logService.trace('[text file model] doUpdateTextModel()', this.resource.toString(true)); // Update model value in a block that ignores content change events for dirty tracking this.ignoreDirtyOnModelContentChange = true; @@ -426,9 +441,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } finally { this.ignoreDirtyOnModelContentChange = false; } - - // Ensure we track the latest saved version ID given that the contents changed - this.updateSavedVersionId(); } private installModelListeners(model: ITextModel): void { @@ -442,11 +454,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private onModelContentChanged(model: ITextModel): void { - this.logService.trace(`[text file model] onModelContentChanged() - enter`, this.resource.toString()); + this.logService.trace(`[text file model] onModelContentChanged() - enter`, this.resource.toString(true)); // In any case increment the version id because it tracks the textual content state of the model at all times this.versionId++; - this.logService.trace(`[text file model] onModelContentChanged() - new versionId ${this.versionId}`, this.resource.toString()); + this.logService.trace(`[text file model] onModelContentChanged() - new versionId ${this.versionId}`, this.resource.toString(true)); // We mark check for a dirty-state change upon model content change, unless: // - explicitly instructed to ignore it (e.g. from model.load()) @@ -456,7 +468,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // The contents changed as a matter of Undo and the version reached matches the saved one // In this case we clear the dirty flag and emit a SAVED event to indicate this state. if (model.getAlternativeVersionId() === this.bufferSavedVersionId) { - this.logService.trace('[text file model] onModelContentChanged() - model content changed back to last saved version', this.resource.toString()); + this.logService.trace('[text file model] onModelContentChanged() - model content changed back to last saved version', this.resource.toString(true)); // Clear flags const wasDirty = this.dirty; @@ -470,7 +482,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Otherwise the content has changed and we signal this as becoming dirty else { - this.logService.trace('[text file model] onModelContentChanged() - model content changed and marked as dirty', this.resource.toString()); + this.logService.trace('[text file model] onModelContentChanged() - model content changed and marked as dirty', this.resource.toString(true)); // Mark as dirty this.setDirty(true); @@ -538,24 +550,24 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } if (this.isReadonly()) { - this.logService.trace('[text file model] save() - ignoring request for readonly resource', this.resource.toString()); + this.logService.trace('[text file model] save() - ignoring request for readonly resource', this.resource.toString(true)); return false; // if model is readonly we do not attempt to save at all } if ( - (this.hasState(ModelState.CONFLICT) || this.hasState(ModelState.ERROR)) && + (this.hasState(TextFileEditorModelState.CONFLICT) || this.hasState(TextFileEditorModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE) ) { - this.logService.trace('[text file model] save() - ignoring auto save request for model that is in conflict or error', this.resource.toString()); + this.logService.trace('[text file model] save() - ignoring auto save request for model that is in conflict or error', this.resource.toString(true)); return false; // if model is in save conflict or error, do not save unless save reason is explicit } // Actually do save and log - this.logService.trace('[text file model] save() - enter', this.resource.toString()); + this.logService.trace('[text file model] save() - enter', this.resource.toString(true)); await this.doSave(options); - this.logService.trace('[text file model] save() - exit', this.resource.toString()); + this.logService.trace('[text file model] save() - exit', this.resource.toString(true)); return true; } @@ -566,7 +578,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } let versionId = this.versionId; - this.logService.trace(`[text file model] doSave(${versionId}) - enter with versionId ${versionId}`, this.resource.toString()); + this.logService.trace(`[text file model] doSave(${versionId}) - enter with versionId ${versionId}`, this.resource.toString(true)); // Lookup any running pending save for this versionId and return it if found // @@ -574,7 +586,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // while the save was not yet finished to disk // if (this.saveSequentializer.hasPending(versionId)) { - this.logService.trace(`[text file model] doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource.toString()); + this.logService.trace(`[text file model] doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource.toString(true)); return this.saveSequentializer.pending; } @@ -583,7 +595,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // // Scenario: user invoked save action even though the model is not dirty if (!options.force && !this.dirty) { - this.logService.trace(`[text file model] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource.toString()); + this.logService.trace(`[text file model] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource.toString(true)); return; } @@ -597,7 +609,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // while the first save has not returned yet. // if (this.saveSequentializer.hasPending()) { - this.logService.trace(`[text file model] doSave(${versionId}) - exit - because busy saving`, this.resource.toString()); + this.logService.trace(`[text file model] doSave(${versionId}) - exit - because busy saving`, this.resource.toString(true)); // Indicate to the save sequentializer that we want to // cancel the pending operation so that ours can run @@ -616,7 +628,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.textEditorModel.pushStackElement(); } - const saveParticipantCancellation = new CancellationTokenSource(); + const saveCancellation = new CancellationTokenSource(); return this.saveSequentializer.setPending(versionId, (async () => { @@ -625,14 +637,26 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // In addition we update our version right after in case it changed because of a model change // // Save participants can also be skipped through API. - if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) { + if (this.isResolved() && !options.skipSaveParticipants) { try { - await this.textFileService.saveParticipant.participate(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveParticipantCancellation.token); + await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); } catch (error) { - // Ignore + this.logService.error(`[text file model] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString(true)); } } + // It is possible that a subsequent save is cancelling this + // running save. As such we return early when we detect that + // However, we do not pass the token into the file service + // because that is an atomic operation currently without + // cancellation support, so we dispose the cancellation if + // it was not cancelled yet. + if (saveCancellation.token.isCancellationRequested) { + return; + } else { + saveCancellation.dispose(); + } + // We have to protect against being disposed at this point. It could be that the save() operation // was triggerd followed by a dispose() operation right after without waiting. Typically we cannot // be disposed if we are dirty, but if we are not dirty, save() and dispose() can still be triggered @@ -668,7 +692,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Save to Disk. We mark the save operation as currently pending with // the latest versionId because it might have changed from a save // participant triggering - this.logService.trace(`[text file model] doSave(${versionId}) - before write()`, this.resource.toString()); + this.logService.trace(`[text file model] doSave(${versionId}) - before write()`, this.resource.toString(true)); const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); const textFileEdiorModel = this; return this.saveSequentializer.setPending(versionId, (async () => { @@ -687,21 +711,20 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.handleSaveError(error, versionId, options); } })()); - })(), () => saveParticipantCancellation.cancel()); + })(), () => saveCancellation.cancel()); } private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void { - this.logService.trace(`[text file model] doSave(${versionId}) - after write()`, this.resource.toString()); // Updated resolved stat with updated stat this.updateLastResolvedFileStat(stat); // Update dirty state unless model has changed meanwhile if (versionId === this.versionId) { - this.logService.trace(`[text file model] handleSaveSuccess(${versionId}) - setting dirty to false because versionId did not change`, this.resource.toString()); + this.logService.trace(`[text file model] handleSaveSuccess(${versionId}) - setting dirty to false because versionId did not change`, this.resource.toString(true)); this.setDirty(false); } else { - this.logService.trace(`[text file model] handleSaveSuccess(${versionId}) - not setting dirty to false because versionId did change meanwhile`, this.resource.toString()); + this.logService.trace(`[text file model] handleSaveSuccess(${versionId}) - not setting dirty to false because versionId did change meanwhile`, this.resource.toString(true)); } // Emit Save Event @@ -709,7 +732,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private handleSaveError(error: Error, versionId: number, options: ITextFileSaveOptions): void { - this.logService.error(`[text file model] handleSaveError(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString()); + this.logService.error(`[text file model] handleSaveError(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString(true)); // Return early if the save() call was made asking to // handle the save error itself. @@ -726,7 +749,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Show to user - this.textFileService.saveErrorHandler.onSaveError(error, this); + this.textFileService.files.saveErrorHandler.onSaveError(error, this); // Emit as event this._onDidSaveError.fire(); @@ -782,19 +805,19 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#endregion - hasState(state: ModelState): boolean { + hasState(state: TextFileEditorModelState): boolean { switch (state) { - case ModelState.CONFLICT: + case TextFileEditorModelState.CONFLICT: return this.inConflictMode; - case ModelState.DIRTY: + case TextFileEditorModelState.DIRTY: return this.dirty; - case ModelState.ERROR: + case TextFileEditorModelState.ERROR: return this.inErrorMode; - case ModelState.ORPHAN: + case TextFileEditorModelState.ORPHAN: return this.inOrphanMode; - case ModelState.PENDING_SAVE: + case TextFileEditorModelState.PENDING_SAVE: return this.saveSequentializer.hasPending(); - case ModelState.SAVED: + case TextFileEditorModelState.SAVED: return !this.dirty; } } @@ -894,6 +917,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } dispose(): void { + this.logService.trace('[text file model] dispose()', this.resource.toString(true)); + this.disposed = true; this.inConflictMode = false; this.inOrphanMode = false; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index a4fbde9a277..a962a5338a7 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -3,53 +3,80 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; 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, IModelLoadOrCreateOptions, ITextFileModelLoadEvent, ITextFileModelSaveEvent } 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'; -import { IFileService, FileChangesEvent } from 'vs/platform/files/common/files'; +import { IFileService, FileChangesEvent, FileOperation } from 'vs/platform/files/common/files'; import { distinct, coalesce } from 'vs/base/common/arrays'; import { ResourceQueue } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { TextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textFileSaveParticipant'; +import { SaveReason } from 'vs/workbench/common/editor'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IWorkingCopyFileService, WorkingCopyFileEvent } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { ITextSnapshot, ITextBufferFactory } from 'vs/editor/common/model'; +import { joinPath, isEqualOrParent, isEqual } from 'vs/base/common/resources'; +import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { - private readonly _onDidLoad = this._register(new Emitter()); + private readonly _onDidCreate = this._register(new Emitter()); + readonly onDidCreate = this._onDidCreate.event; + + private readonly _onDidLoad = this._register(new Emitter()); readonly onDidLoad = this._onDidLoad.event; - private readonly _onDidChangeDirty = this._register(new Emitter()); + private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onDidSaveError = this._register(new Emitter()); + private readonly _onDidSaveError = this._register(new Emitter()); readonly onDidSaveError = this._onDidSaveError.event; - private readonly _onDidSave = this._register(new Emitter()); + private readonly _onDidSave = this._register(new Emitter()); readonly onDidSave = this._onDidSave.event; - private readonly _onDidRevert = this._register(new Emitter()); + private readonly _onDidRevert = this._register(new Emitter()); readonly onDidRevert = this._onDidRevert.event; - private readonly _onDidChangeEncoding = this._register(new Emitter()); + private readonly _onDidChangeEncoding = this._register(new Emitter()); readonly onDidChangeEncoding = this._onDidChangeEncoding.event; - private readonly _onDidChangeOrphaned = this._register(new Emitter()); - readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event; - - private readonly mapResourceToModel = new ResourceMap(); + private readonly mapResourceToModel = new ResourceMap(); private readonly mapResourceToModelListeners = new ResourceMap(); private readonly mapResourceToDisposeListener = new ResourceMap(); - private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); + private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); private readonly modelLoadQueue = this._register(new ResourceQueue()); + saveErrorHandler = (() => { + const notificationService = this.notificationService; + + return { + onSaveError(error: Error, model: ITextFileEditorModel): void { + notificationService.error(localize('genericSaveError', "Failed to save '{0}': {1}", model.name, toErrorMessage(error, false))); + } + }; + })(); + + get models(): TextFileEditorModel[] { + return this.mapResourceToModel.values(); + } + constructor( @ILifecycleService private readonly lifecycleService: ILifecycleService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @INotificationService private readonly notificationService: INotificationService, + @IWorkingCopyFileService private readonly workingCopyFileService: IWorkingCopyFileService ) { super(); @@ -59,25 +86,32 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private registerListeners(): void { // Update models from file change events - this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); + + // Working copy operations + this._register(this.workingCopyFileService.onWillRunWorkingCopyFileOperation(e => this.onWillRunWorkingCopyFileOperation(e))); + this._register(this.workingCopyFileService.onDidFailWorkingCopyFileOperation(e => this.onDidFailWorkingCopyFileOperation(e))); + this._register(this.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => this.onDidRunWorkingCopyFileOperation(e))); // Lifecycle this.lifecycleService.onShutdown(this.dispose, this); } - private onFileChanges(e: FileChangesEvent): void { + private onDidFilesChange(e: FileChangesEvent): void { // Collect distinct (saved) models to update. // // Note: we also consider the added event because it could be that a file was added // and updated right after. - distinct(coalesce([...e.getUpdated(), ...e.getAdded()] - .map(({ resource }) => this.get(resource))) - .filter(model => model && !model.isDirty()), model => model.resource.toString()) - .forEach(model => this.queueModelLoad(model)); + distinct( + coalesce( + [...e.getUpdated(), ...e.getAdded()].map(({ resource }) => this.get(resource)) + ).filter(model => model && model.isResolved() && !model.isDirty()), + model => model.resource.toString() + ).forEach(model => this.queueModelLoad(model)); } - private queueModelLoad(model: ITextFileEditorModel): void { + private queueModelLoad(model: TextFileEditorModel): void { // Load model to update (use a queue to prevent accumulation of loads // when the load actually takes long. At most we only want the queue @@ -94,11 +128,121 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } - get(resource: URI): ITextFileEditorModel | undefined { + private readonly mapCorrelationIdToModelsToRestore = new Map(); + + private onWillRunWorkingCopyFileOperation(e: WorkingCopyFileEvent): void { + + // Move / Copy: remember models to restore after the operation + const source = e.source; + if (source && (e.operation === FileOperation.COPY || e.operation === FileOperation.MOVE)) { + + // find all models that related to either source or target (can be many if resource is a folder) + const sourceModels: TextFileEditorModel[] = []; + const targetModels: TextFileEditorModel[] = []; + for (const model of this.models) { + const resource = model.resource; + + if (isEqualOrParent(resource, e.target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { + targetModels.push(model); + } + + if (isEqualOrParent(resource, source)) { + sourceModels.push(model); + } + } + + // remember each source model to load again after move is done + // with optional content to restore if it was dirty + const modelsToRestore: { source: URI, target: URI, snapshot?: ITextSnapshot; mode?: string; encoding?: string; }[] = []; + for (const sourceModel of sourceModels) { + const sourceModelResource = sourceModel.resource; + + // If the source is the actual model, just use target as new resource + let targetModelResource: URI; + if (isEqual(sourceModelResource, e.source)) { + targetModelResource = e.target; + } + + // Otherwise a parent folder of the source is being moved, so we need + // to compute the target resource based on that + else { + targetModelResource = joinPath(e.target, sourceModelResource.path.substr(source.path.length + 1)); + } + + modelsToRestore.push({ + source: sourceModelResource, + target: targetModelResource, + mode: sourceModel.getMode(), + encoding: sourceModel.getEncoding(), + snapshot: sourceModel.isDirty() ? sourceModel.createSnapshot() : undefined + }); + } + + this.mapCorrelationIdToModelsToRestore.set(e.correlationId, modelsToRestore); + } + } + + private onDidFailWorkingCopyFileOperation(e: WorkingCopyFileEvent): void { + + // Move / Copy: restore dirty flag on models to restore that were dirty + if ((e.operation === FileOperation.COPY || e.operation === FileOperation.MOVE)) { + const modelsToRestore = this.mapCorrelationIdToModelsToRestore.get(e.correlationId); + if (modelsToRestore) { + this.mapCorrelationIdToModelsToRestore.delete(e.correlationId); + + modelsToRestore.forEach(model => { + // snapshot presence means this model used to be dirty + if (model.snapshot) { + this.get(model.source)?.setDirty(true); + } + }); + } + } + } + + private onDidRunWorkingCopyFileOperation(e: WorkingCopyFileEvent): void { + + // Move / Copy: restore models that were loaded before the operation took place + if ((e.operation === FileOperation.COPY || e.operation === FileOperation.MOVE)) { + e.waitUntil((async () => { + const modelsToRestore = this.mapCorrelationIdToModelsToRestore.get(e.correlationId); + if (modelsToRestore) { + this.mapCorrelationIdToModelsToRestore.delete(e.correlationId); + + await Promise.all(modelsToRestore.map(async modelToRestore => { + + // restore the model, forcing a reload. this is important because + // we know the file has changed on disk after the move and the + // model might have still existed with the previous state. this + // ensures we are not tracking a stale state. + const restoredModel = await this.resolve(modelToRestore.target, { reload: { async: false }, encoding: modelToRestore.encoding }); + + // restore previous dirty content if any and ensure to mark the model as dirty + let textBufferFactory: ITextBufferFactory | undefined = undefined; + if (modelToRestore.snapshot) { + textBufferFactory = createTextBufferFactoryFromSnapshot(modelToRestore.snapshot); + } + + // restore previous mode only if the mode is now unspecified + let preferredMode: string | undefined = undefined; + if (restoredModel.getMode() === PLAINTEXT_MODE_ID && modelToRestore.mode !== PLAINTEXT_MODE_ID) { + preferredMode = modelToRestore.mode; + } + + if (textBufferFactory || preferredMode) { + restoredModel.updateTextEditorModel(textBufferFactory, preferredMode); + } + })); + } + })()); + } + } + + get(resource: URI): TextFileEditorModel | undefined { return this.mapResourceToModel.get(resource); } - async resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise { + async resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise { // Return early if model is currently being loaded const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource); @@ -106,10 +250,11 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return pendingLoad; } - let modelPromise: Promise; + let modelPromise: Promise; + let model = this.get(resource); + let didCreateModel = false; // Model exists - let model = this.get(resource); if (model) { if (options?.reload) { @@ -130,36 +275,34 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Model does not exist else { + didCreateModel = true; + const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined); modelPromise = model.load(options); - // Install model listeners - const listeners = new DisposableStore(); - listeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason }))); - listeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel))); - listeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel))); - listeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason }))); - listeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel))); - listeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel))); - listeners.add(model.onDidChangeOrphaned(() => this._onDidChangeOrphaned.fire(newModel))); - - this.mapResourceToModelListeners.set(resource, listeners); + this.registerModel(newModel); } // Store pending loads to avoid race conditions this.mapResourceToPendingModelLoaders.set(resource, modelPromise); + // Make known to manager (if not already known) + this.add(resource, 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 { const resolvedModel = await modelPromise; - // Make known to manager (if not already known) - this.add(resource, resolvedModel); - - // Model can be dirty if a backup was restored, so we make sure to have this event delivered - if (resolvedModel.isDirty()) { - this._onDidChangeDirty.fire(resolvedModel); - } - // Remove from pending loads this.mapResourceToPendingModelLoaders.delete(resource); @@ -168,6 +311,12 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE resolvedModel.setMode(options.mode); } + // Model can be dirty if a backup was restored, so we make sure to + // have this event delivered if we created the model here + if (didCreateModel && resolvedModel.isDirty()) { + this._onDidChangeDirty.fire(resolvedModel); + } + return resolvedModel; } catch (error) { @@ -183,18 +332,22 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } - getAll(filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] { - const res: ITextFileEditorModel[] = []; - this.mapResourceToModel.forEach(model => { - if (!filter || filter(model)) { - res.push(model); - } - }); + private registerModel(model: TextFileEditorModel): void { - return res; + // 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: ITextFileEditorModel): void { + add(resource: URI, model: TextFileEditorModel): void { const knownModel = this.mapResourceToModel.get(resource); if (knownModel === model) { return; // already cached @@ -227,6 +380,20 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } + //#region Save participants + + private readonly saveParticipants = this._register(this.instantiationService.createInstance(TextFileSaveParticipant)); + + addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable { + return this.saveParticipants.addSaveParticipant(participant); + } + + runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { + return this.saveParticipants.participate(model, context, token); + } + + //#endregion + clear(): void { // model caches diff --git a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts new file mode 100644 index 00000000000..037f389fed8 --- /dev/null +++ b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { 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, 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 { + + private readonly saveParticipants: ITextFileSaveParticipant[] = []; + + constructor( + @IProgressService private readonly progressService: IProgressService, + @ILogService private readonly logService: ILogService + ) { + super(); + } + + addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable { + const remove = insert(this.saveParticipants, participant); + + return toDisposable(() => remove()); + } + + participate(model: ITextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { + const cts = new CancellationTokenSource(token); + + return this.progressService.withProgress({ + title: localize('saveParticipants', "Saving '{0}'", model.name), + location: ProgressLocation.Notification, + cancellable: true, + delay: model.isDirty() ? 3000 : 5000 + }, async progress => { + + // undoStop before participation + model.textEditorModel?.pushStackElement(); + + for (const saveParticipant of this.saveParticipants) { + if (cts.token.isCancellationRequested || !model.textEditorModel /* disposed */) { + break; + } + + try { + const promise = saveParticipant.participate(model, context, progress, cts.token); + await raceCancellation(promise, cts.token); + } catch (err) { + this.logService.warn(err); + } + } + + // undoStop after participation + model.textEditorModel?.pushStackElement(); + }, () => { + // user cancel + cts.dispose(true); + }); + } + + dispose(): void { + this.saveParticipants.splice(0, this.saveParticipants.length); + } +} diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index c3c1f68d02c..99154e4353a 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { Event, IWaitUntil } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IEncodingSupport, IModeSupport, ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; -import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult, FileOperation } from 'vs/platform/files/common/files'; +import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; @@ -17,23 +17,18 @@ import { isNative } from 'vs/base/common/platform'; import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; export const ITextFileService = createDecorator('textFileService'); +export interface TextFileCreateEvent extends IWaitUntil { + readonly resource: URI; +} + export interface ITextFileService extends IDisposable { _serviceBrand: undefined; - /** - * An event that is fired before attempting a certain file operation. - */ - readonly onWillRunOperation: Event; - - /** - * An event that is fired after a file operation has been performed. - */ - readonly onDidRunOperation: Event; - /** * Access to the manager of text file editor models providing further * methods to work with them. @@ -51,17 +46,6 @@ export interface ITextFileService extends IDisposable { */ readonly encoding: IResourceEncodings; - /** - * The handler that should be called when saving fails. Can be overridden - * to handle save errors in a custom way. - */ - saveErrorHandler: ISaveErrorHandler; - - /** - * The save participant if any. By default, no save participant is registered. - */ - saveParticipant: ISaveParticipant | undefined; - /** * A resource is dirty if it has unsaved changes or is an untitled file not yet saved. * @@ -94,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. @@ -111,41 +95,16 @@ export interface ITextFileService extends IDisposable { */ write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise; + /** + * An event that is fired after a text file has been created. + */ + readonly onDidCreateTextFile: Event; + /** * Create a file. If the file exists it will be overwritten with the contents if * the options enable to overwrite. */ create(resource: URI, contents?: string | ITextSnapshot, options?: { overwrite?: boolean }): Promise; - - /** - * Move a file. If the file is dirty, its contents will be preserved and restored. - */ - move(source: URI, target: URI, overwrite?: boolean): Promise; - - /** - * Copy a file. If the file is dirty, its contents will be preserved and restored. - */ - copy(source: URI, target: URI, overwrite?: boolean): Promise; - - /** - * Delete a file. If the file is dirty, it will get reverted and then deleted from disk. - */ - delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise; -} - -export interface FileOperationWillRunEvent extends IWaitUntil { - operation: FileOperation; - target: URI; - source?: URI; -} - -export class FileOperationDidRunEvent { - - constructor( - readonly operation: FileOperation, - readonly target: URI, - readonly source?: URI | undefined - ) { } } export interface IReadTextFileOptions extends IReadFileOptions { @@ -197,13 +156,18 @@ export const enum TextFileOperationResult { } export class TextFileOperationError extends FileOperationError { - constructor(message: string, public textFileOperationResult: TextFileOperationResult, public options?: IReadTextFileOptions & IWriteTextFileOptions) { - super(message, FileOperationResult.FILE_OTHER_ERROR); - } static isTextFileOperationError(obj: unknown): obj is TextFileOperationError { return obj instanceof Error && !isUndefinedOrNull((obj as TextFileOperationError).textFileOperationResult); } + + constructor( + message: string, + public textFileOperationResult: TextFileOperationResult, + public options?: IReadTextFileOptions & IWriteTextFileOptions + ) { + super(message, FileOperationResult.FILE_OTHER_ERROR); + } } export interface IResourceEncodings { @@ -226,18 +190,10 @@ export interface ISaveErrorHandler { onSaveError(error: Error, model: ITextFileEditorModel): void; } -export interface ISaveParticipant { - - /** - * Participate in a save of a model. Allows to change the model before it is being saved to disk. - */ - participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise; -} - /** * States the text file editor model can be in. */ -export const enum ModelState { +export const enum TextFileEditorModelState { /** * A model is saved. @@ -272,17 +228,7 @@ export const enum ModelState { ERROR } -export interface ITextFileOperationResult { - results: IResult[]; -} - -export interface IResult { - source: URI; - target?: URI; - error?: boolean; -} - -export const enum LoadReason { +export const enum TextFileLoadReason { EDITOR = 1, REFERENCE = 2, OTHER = 3 @@ -312,12 +258,12 @@ export interface ITextFileStreamContent extends IBaseTextFileContent { value: ITextBufferFactory; } -export interface IModelLoadOrCreateOptions { +export interface ITextFileEditorModelLoadOrCreateOptions { /** * Context why the model is being loaded or created. */ - reason?: LoadReason; + reason?: TextFileLoadReason; /** * The language mode to use for the model text content. @@ -347,43 +293,99 @@ export interface IModelLoadOrCreateOptions { allowBinary?: boolean; } -export interface ITextFileModelSaveEvent { +export interface ITextFileSaveEvent { model: ITextFileEditorModel; reason: SaveReason; } -export interface ITextFileModelLoadEvent { +export interface ITextFileLoadEvent { model: ITextFileEditorModel; - reason: LoadReason; + reason: TextFileLoadReason; +} + +export interface ITextFileSaveParticipant { + + /** + * Participate in a save of a model. Allows to change the model + * before it is being saved to disk. + */ + participate( + model: ITextFileEditorModel, + context: { reason: SaveReason }, + progress: IProgress, + token: CancellationToken + ): Promise; } export interface ITextFileEditorModelManager { - readonly onDidLoad: Event; + readonly onDidCreate: Event; + readonly onDidLoad: Event; readonly onDidChangeDirty: Event; - readonly onDidSaveError: Event; - readonly onDidSave: Event; - readonly onDidRevert: Event; readonly onDidChangeEncoding: Event; - readonly onDidChangeOrphaned: Event; + readonly onDidSaveError: Event; + readonly onDidSave: Event; + readonly onDidRevert: Event; + readonly models: ITextFileEditorModel[]; + + saveErrorHandler: ISaveErrorHandler; + + /** + * Returns the text file editor model for the provided resource + * or undefined if none. + */ get(resource: URI): ITextFileEditorModel | undefined; - getAll(): ITextFileEditorModel[]; - resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise; + /** + * Allows to load a text file model from disk. + */ + resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise; + + /** + * Adds a participant for saving text file models. + */ + addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable; + + /** + * Runs the registered save participants on the provided model. + */ + runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise disposeModel(model: ITextFileEditorModel): void; } export interface ITextFileSaveOptions extends ISaveOptions { + + /** + * Makes the file writable if it is readonly. + */ overwriteReadonly?: boolean; + + /** + * Overwrite the encoding of the file on disk as configured. + */ overwriteEncoding?: boolean; + + /** + * Save the file with elevated privileges. + * + * Note: This may not be supported in all environments. + */ writeElevated?: boolean; + + /** + * Allows to write to a file even if it has been modified on disk. + */ ignoreModifiedSince?: boolean; + + /** + * If set, will bubble up the error to the caller instead of handling it. + */ ignoreErrorHandler?: boolean; } -export interface ILoadOptions { +export interface ITextFileLoadOptions { /** * Go to disk bypassing any cache of the model if any. @@ -398,37 +400,29 @@ export interface ILoadOptions { /** * Context why the model is being loaded. */ - reason?: LoadReason; + reason?: TextFileLoadReason; } export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy { readonly onDidChangeContent: Event; - readonly onDidLoad: Event; readonly onDidSaveError: Event; - readonly onDidSave: Event; - readonly onDidRevert: Event; - readonly onDidChangeEncoding: Event; readonly onDidChangeOrphaned: Event; - hasState(state: ModelState): boolean; + hasState(state: TextFileEditorModelState): boolean; updatePreferredEncoding(encoding: string | undefined): void; save(options?: ITextFileSaveOptions): Promise; + revert(options?: IRevertOptions): Promise; - load(options?: ILoadOptions): Promise; - - revert(options?: IRevertOptions): Promise; + load(options?: ITextFileLoadOptions): Promise; isDirty(): this is IResolvedTextFileEditorModel; - setDirty(dirty: boolean): void; - getMode(): string | undefined; isResolved(): this is IResolvedTextFileEditorModel; - isDisposed(): boolean; } diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 34ffc230de4..999055c5fb9 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -33,12 +33,13 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { assign } from 'vs/base/common/objects'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; 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 +49,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, @@ -56,10 +57,11 @@ export class NativeTextFileService extends AbstractTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @INotificationService notificationService: INotificationService, - @IRemotePathService remotePathService: IRemotePathService + @IRemotePathService remotePathService: IRemotePathService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, + @ILogService private readonly logService: ILogService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, notificationService, remotePathService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, remotePathService, workingCopyFileService); } private _encoding: EncodingOracle | undefined; @@ -72,16 +74,15 @@ export class NativeTextFileService extends AbstractTextFileService { } async read(resource: URI, options?: IReadTextFileOptions): Promise { - const [bufferStream, decoder] = await this.doRead(resource, - assign({ - // optimization: since we know that the caller does not - // care about buffering, we indicate this to the reader. - // this reduces all the overhead the buffered reading - // has (open, read, close) if the provider supports - // unbuffered reading. - preferUnbuffered: true - }, options || Object.create(null)) - ); + const [bufferStream, decoder] = await this.doRead(resource, { + ...options, + // optimization: since we know that the caller does not + // care about buffering, we indicate this to the reader. + // this reduces all the overhead the buffered reading + // has (open, read, close) if the provider supports + // unbuffered reading. + preferUnbuffered: true + }); return { ...bufferStream, @@ -297,8 +298,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 c835deecece..1382a73a73f 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -7,26 +7,15 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, ModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { createFileInput, TestFileService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TextFileEditorModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +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, IFileService } from 'vs/platform/files/common/files'; -import { IModelService } from 'vs/editor/common/services/modelService'; +import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { timeout } from 'vs/base/common/async'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { assertIsDefined } from 'vs/base/common/types'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; - -class ServiceAccessor { - constructor( - @ITextFileService public readonly textFileService: TestTextFileService, - @IModelService public readonly modelService: IModelService, - @IFileService public readonly fileService: TestFileService, - @IWorkingCopyService public readonly workingCopyService: IWorkingCopyService - ) { - } -} +import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; function getLastModifiedTime(model: TextFileEditorModel): number { const stat = model.getStat(); @@ -34,22 +23,15 @@ function getLastModifiedTime(model: TextFileEditorModel): number { return stat ? stat.mtime : -1; } -class TestTextFileEditorModel extends TextFileEditorModel { - - isReadonly(): boolean { - return true; - } -} - suite('Files - TextFileEditorModel', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; let content: string; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); content = accessor.fileService.getContent(); }); @@ -74,12 +56,12 @@ suite('Files - TextFileEditorModel', () => { let onDidChangeDirtyCounter = 0; model.onDidChangeDirty(() => onDidChangeDirtyCounter++); - model.textEditorModel?.setValue('bar'); + model.updateTextEditorModel(createTextBufferFactory('bar')); assert.equal(onDidChangeContentCounter, 1); assert.equal(onDidChangeDirtyCounter, 1); - model.textEditorModel?.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); assert.equal(onDidChangeContentCounter, 2); assert.equal(onDidChangeDirtyCounter, 1); @@ -98,16 +80,19 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.dirtyCount, 0); - model.textEditorModel!.setValue('bar'); + let savedEvent = false; + model.onDidSave(() => savedEvent = true); + + await model.save(); + assert.ok(!savedEvent); + + model.updateTextEditorModel(createTextBufferFactory('bar')); assert.ok(getLastModifiedTime(model) <= Date.now()); - assert.ok(model.hasState(ModelState.DIRTY)); + assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); assert.equal(accessor.workingCopyService.dirtyCount, 1); assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - let savedEvent = false; - model.onDidSave(e => savedEvent = true); - let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { if (e.resource.toString() === model.resource.toString()) { @@ -116,11 +101,11 @@ suite('Files - TextFileEditorModel', () => { }); const pendingSave = model.save(); - assert.ok(model.hasState(ModelState.PENDING_SAVE)); + assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); await pendingSave; - assert.ok(model.hasState(ModelState.SAVED)); + assert.ok(model.hasState(TextFileEditorModelState.SAVED)); assert.ok(!model.isDirty()); assert.ok(savedEvent); assert.ok(workingCopyEvent); @@ -128,6 +113,11 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.dirtyCount, 0); assert.equal(accessor.workingCopyService.isDirty(model.resource), false); + savedEvent = false; + + await model.save({ force: true }); + assert.ok(savedEvent); + model.dispose(); assert.ok(!accessor.modelService.getModel(model.resource)); }); @@ -138,7 +128,7 @@ suite('Files - TextFileEditorModel', () => { await model.load(); let savedEvent = false; - model.onDidSave(e => savedEvent = true); + model.onDidSave(() => savedEvent = true); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -161,19 +151,19 @@ suite('Files - TextFileEditorModel', () => { await model.load(); - model.textEditorModel!.setValue('bar'); + model.updateTextEditorModel(createTextBufferFactory('bar')); let saveErrorEvent = false; - model.onDidSaveError(e => saveErrorEvent = true); + model.onDidSaveError(() => saveErrorEvent = true); accessor.fileService.writeShouldThrowError = new Error('failed to write'); try { const pendingSave = model.save(); - assert.ok(model.hasState(ModelState.PENDING_SAVE)); + assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); await pendingSave; - assert.ok(model.hasState(ModelState.ERROR)); + assert.ok(model.hasState(TextFileEditorModelState.ERROR)); assert.ok(model.isDirty()); assert.ok(saveErrorEvent); @@ -191,19 +181,19 @@ suite('Files - TextFileEditorModel', () => { await model.load(); - model.textEditorModel!.setValue('bar'); + model.updateTextEditorModel(createTextBufferFactory('bar')); let saveErrorEvent = false; - model.onDidSaveError(e => saveErrorEvent = true); + model.onDidSaveError(() => saveErrorEvent = true); accessor.fileService.writeShouldThrowError = new FileOperationError('save conflict', FileOperationResult.FILE_MODIFIED_SINCE); try { const pendingSave = model.save(); - assert.ok(model.hasState(ModelState.PENDING_SAVE)); + assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); await pendingSave; - assert.ok(model.hasState(ModelState.CONFLICT)); + assert.ok(model.hasState(TextFileEditorModelState.CONFLICT)); assert.ok(model.isDirty()); assert.ok(saveErrorEvent); @@ -220,7 +210,7 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); let encodingEvent = false; - model.onDidChangeEncoding(e => encodingEvent = true); + model.onDidChangeEncoding(() => encodingEvent = true); model.setEncoding('utf8', EncodingMode.Encode); // no-op assert.equal(getLastModifiedTime(model), -1); @@ -273,10 +263,10 @@ suite('Files - TextFileEditorModel', () => { test('Load does not trigger save', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined); - assert.ok(model.hasState(ModelState.SAVED)); + assert.ok(model.hasState(TextFileEditorModelState.SAVED)); - model.onDidSave(e => assert.fail()); - model.onDidChangeDirty(e => assert.fail()); + model.onDidSave(() => assert.fail()); + model.onDidChangeDirty(() => assert.fail()); await model.load(); assert.ok(model.isResolved()); @@ -288,9 +278,9 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); - assert.ok(model.hasState(ModelState.DIRTY)); + assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); await model.load(); assert.ok(model.isDirty()); @@ -302,7 +292,7 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidRevert(e => eventCounter++); + model.onDidRevert(() => eventCounter++); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -312,7 +302,7 @@ suite('Files - TextFileEditorModel', () => { }); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); assert.equal(accessor.workingCopyService.dirtyCount, 1); @@ -335,7 +325,7 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidRevert(e => eventCounter++); + model.onDidRevert(() => eventCounter++); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -345,7 +335,7 @@ suite('Files - TextFileEditorModel', () => { }); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); assert.equal(accessor.workingCopyService.dirtyCount, 1); @@ -363,6 +353,16 @@ suite('Files - TextFileEditorModel', () => { model.dispose(); }); + test('Undo to saved state turns model non-dirty', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + await model.load(); + model.updateTextEditorModel(createTextBufferFactory('Hello Text')); + assert.ok(model.isDirty()); + + model.textEditorModel!.undo(); + assert.ok(!model.isDirty()); + }); + test('Load and undo turns model dirty', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); @@ -385,13 +385,13 @@ suite('Files - TextFileEditorModel', () => { assert.ok(!model.isDirty()); // needs to be resolved await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(model.isDirty()); await model.revert({ soft: true }); assert.ok(!model.isDirty()); - model.onDidChangeDirty(e => eventCounter++); + model.onDidChangeDirty(() => eventCounter++); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -412,7 +412,7 @@ suite('Files - TextFileEditorModel', () => { model.dispose(); }); - test('No Dirty for readonly models', async function () { + test('No Dirty or saving for readonly models', async function () { let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { if (e.resource.toString() === model.resource.toString()) { @@ -420,12 +420,20 @@ suite('Files - TextFileEditorModel', () => { } }); - const model = instantiationService.createInstance(TestTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model = instantiationService.createInstance(TestReadonlyTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + let saveEvent = false; + model.onDidSave(() => { + saveEvent = true; + }); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(!model.isDirty()); + await model.save({ force: true }); + assert.equal(saveEvent, false); + await model.revert({ soft: true }); assert.ok(!model.isDirty()); @@ -461,13 +469,13 @@ 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; - model1.textEditorModel!.setValue('foo'); + model1.updateTextEditorModel(createTextBufferFactory('foo')); const m1Mtime = assertIsDefined(model1.getStat()).mtime; const m2Mtime = assertIsDefined(model2.getStat()).mtime; @@ -477,7 +485,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); - model2.textEditorModel!.setValue('foo'); + model2.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); await timeout(10); @@ -496,61 +504,106 @@ suite('Files - TextFileEditorModel', () => { let eventCounter = 0; const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidSave(e => { - assert.equal(snapshotToString(model.createSnapshot()!), 'bar'); + model.onDidSave(() => { + assert.equal(snapshotToString(model.createSnapshot()!), eventCounter === 1 ? 'bar' : 'foobar'); assert.ok(!model.isDirty()); eventCounter++; }); - accessor.textFileService.saveParticipant = { + const participant = accessor.textFileService.files.addSaveParticipant({ participate: async model => { assert.ok(model.isDirty()); - model.textEditorModel!.setValue('bar'); + (model as TextFileEditorModel).updateTextEditorModel(createTextBufferFactory('bar')); assert.ok(model.isDirty()); eventCounter++; } - }; + }); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); + assert.ok(model.isDirty()); await model.save(); - model.dispose(); assert.equal(eventCounter, 2); + + participant.dispose(); + model.updateTextEditorModel(createTextBufferFactory('foobar')); + assert.ok(model.isDirty()); + + await model.save(); + assert.equal(eventCounter, 3); + + model.dispose(); + }); + + test('Save Participant - skip', async function () { + let eventCounter = 0; + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + const participant = accessor.textFileService.files.addSaveParticipant({ + participate: async () => { + eventCounter++; + } + }); + + await model.load(); + model.updateTextEditorModel(createTextBufferFactory('foo')); + + await model.save({ skipSaveParticipants: true }); + assert.equal(eventCounter, 0); + + participant.dispose(); + model.dispose(); }); test('Save Participant, async participant', async function () { + let eventCounter = 0; const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - accessor.textFileService.saveParticipant = { - participate: (model) => { + model.onDidSave(() => { + assert.ok(!model.isDirty()); + eventCounter++; + }); + + const participant = accessor.textFileService.files.addSaveParticipant({ + participate: model => { + assert.ok(model.isDirty()); + (model as TextFileEditorModel).updateTextEditorModel(createTextBufferFactory('bar')); + assert.ok(model.isDirty()); + eventCounter++; + return timeout(10); } - }; + }); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); const now = Date.now(); await model.save(); + assert.equal(eventCounter, 2); assert.ok(Date.now() - now >= 10); + model.dispose(); + participant.dispose(); }); test('Save Participant, bad participant', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - accessor.textFileService.saveParticipant = { - participate: async model => { + const participant = accessor.textFileService.files.addSaveParticipant({ + participate: async () => { new Error('boom'); } - }; + }); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); await model.save(); + model.dispose(); + participant.dispose(); }); test('Save Participant, participant cancelled when saved again', async function () { @@ -558,28 +611,35 @@ suite('Files - TextFileEditorModel', () => { let participations: boolean[] = []; - accessor.textFileService.saveParticipant = { - participate: async model => { + const participant = accessor.textFileService.files.addSaveParticipant({ + participate: async (model, context, progress, token) => { await timeout(10); - participations.push(true); + + if (!token.isCancellationRequested) { + participations.push(true); + } } - }; + }); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); const p1 = model.save(); - model.textEditorModel!.setValue('foo 1'); + model.updateTextEditorModel(createTextBufferFactory('foo 1')); const p2 = model.save(); - model.textEditorModel!.setValue('foo 2'); - await model.save(); + model.updateTextEditorModel(createTextBufferFactory('foo 2')); + const p3 = model.save(); - await p1; - await p2; + model.updateTextEditorModel(createTextBufferFactory('foo 3')); + const p4 = model.save(); + + await Promise.all([p1, p2, p3, p4]); assert.equal(participations.length, 1); + model.dispose(); + participant.dispose(); }); test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () { @@ -602,7 +662,7 @@ suite('Files - TextFileEditorModel', () => { let savePromise: Promise; let breakLoop = false; - accessor.textFileService.saveParticipant = { + const participant = accessor.textFileService.files.addSaveParticipant({ participate: async model => { if (breakLoop) { return; @@ -618,12 +678,14 @@ suite('Files - TextFileEditorModel', () => { // assert that this is the same promise as the outer one assert.equal(savePromise, newSavePromise); } - }; + }); await model.load(); - model.textEditorModel!.setValue('foo'); + model.updateTextEditorModel(createTextBufferFactory('foo')); savePromise = model.save(); await savePromise; + + participant.dispose(); } }); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts index ac3289d69ca..ec0904baeb6 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts @@ -7,29 +7,22 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; -import { IModelService } from 'vs/editor/common/services/modelService'; +import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { toResource } from 'vs/base/test/common/utils'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; - -class ServiceAccessor { - constructor( - @IFileService public fileService: TestFileService, - @IModelService public modelService: IModelService - ) { - } -} +import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; suite('Files - TextFileEditorModelManager', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('add, remove, clear, get, getAll', function () { @@ -50,7 +43,7 @@ suite('Files - TextFileEditorModelManager', () => { assert.ok(!manager.get(fileUpper)); - let results = manager.getAll(); + let results = manager.models; assert.strictEqual(3, results.length); let result = manager.get(URI.file('/yes')); @@ -67,19 +60,19 @@ suite('Files - TextFileEditorModelManager', () => { manager.remove(URI.file('')); - results = manager.getAll(); + results = manager.models; assert.strictEqual(3, results.length); manager.remove(URI.file('/some/other.html')); - results = manager.getAll(); + results = manager.models; assert.strictEqual(2, results.length); manager.remove(fileUpper); - results = manager.getAll(); + results = manager.models; assert.strictEqual(2, results.length); manager.clear(); - results = manager.getAll(); + results = manager.models; assert.strictEqual(0, results.length); model1.dispose(); @@ -92,7 +85,15 @@ suite('Files - TextFileEditorModelManager', () => { const resource = URI.file('/test.html'); const encoding = 'utf8'; - const model = await manager.resolve(resource, { encoding }); + const events: ITextFileEditorModel[] = []; + const listener = manager.onDidCreate(model => { + events.push(model); + }); + + const modelPromise = manager.resolve(resource, { encoding }); + assert.ok(manager.get(resource)); // model known even before resolved() + + const model = await modelPromise; assert.ok(model); assert.equal(model.getEncoding(), encoding); assert.equal(manager.get(resource), model); @@ -105,6 +106,12 @@ suite('Files - TextFileEditorModelManager', () => { assert.notEqual(model3, model2); assert.equal(manager.get(resource), model3); model3.dispose(); + + assert.equal(events.length, 2); + assert.equal(events[0].resource.toString(), model.resource.toString()); + assert.equal(events[1].resource.toString(), model2.resource.toString()); + + listener.dispose(); }); test('removed from cache when model disposed', function () { @@ -183,11 +190,11 @@ suite('Files - TextFileEditorModelManager', () => { const model2 = await manager.resolve(resource2, { encoding: 'utf8' }); assert.equal(loadedCounter, 2); - model1.textEditorModel!.setValue('changed'); + model1.updateTextEditorModel(createTextBufferFactory('changed')); model1.updatePreferredEncoding('utf16'); await model1.revert(); - model1.textEditorModel!.setValue('changed again'); + model1.updateTextEditorModel(createTextBufferFactory('changed again')); await model1.save(); model1.dispose(); @@ -224,7 +231,7 @@ suite('Files - TextFileEditorModelManager', () => { const resource = toResource.call(this, '/path/index_something.txt'); const model = await manager.resolve(resource, { encoding: 'utf8' }); - model.textEditorModel!.setValue('make dirty'); + model.updateTextEditorModel(createTextBufferFactory('make dirty')); manager.disposeModel((model as TextFileEditorModel)); assert.ok(!model.isDisposed()); model.revert({ soft: true }); 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 d7191832368..ab4d13394e2 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts @@ -2,50 +2,27 @@ * 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 { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { TestLifecycleService, TestContextService, TestFileService, TestFilesConfigurationService, TestFileDialogService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; - -class ServiceAccessor { - constructor( - @ILifecycleService public lifecycleService: TestLifecycleService, - @ITextFileService public textFileService: TestTextFileService, - @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, - @IWorkspaceContextService public contextService: TestContextService, - @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService, - @IFileDialogService public fileDialogService: TestFileDialogService - ) { - } -} suite('Files - TextFileService', () => { let instantiationService: IInstantiationService; let model: TextFileEditorModel; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { - if (model) { - model.dispose(); - } + model?.dispose(); (accessor.textFileService.files).dispose(); }); @@ -120,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)); }); @@ -133,71 +109,26 @@ suite('Files - TextFileService', () => { model!.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); + let eventCounter = 0; + + const disposable1 = accessor.workingCopyFileService.addFileOperationParticipant({ + participate: async target => { + assert.equal(target.toString(), model.resource.toString()); + eventCounter++; + } + }); + + const disposable2 = accessor.textFileService.onDidCreateTextFile(e => { + assert.equal(e.resource.toString(), model.resource.toString()); + eventCounter++; + }); await accessor.textFileService.create(model.resource, 'Foo'); assert.ok(!accessor.textFileService.isDirty(model.resource)); + + assert.equal(eventCounter, 2); + + disposable1.dispose(); + disposable2.dispose(); }); - - test('delete - dirty file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); - - await model.load(); - model!.textEditorModel!.setValue('foo'); - assert.ok(accessor.textFileService.isDirty(model.resource)); - - await accessor.textFileService.delete(model.resource); - assert.ok(!accessor.textFileService.isDirty(model.resource)); - }); - - test('move - dirty file', async function () { - await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true); - }); - - test('move - dirty file (target exists and is dirty)', async function () { - await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true, true); - }); - - test('copy - dirty file', async function () { - await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), false); - }); - - test('copy - dirty file (target exists and is dirty)', async function () { - await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), false, true); - }); - - async function testMoveOrCopy(source: URI, target: URI, move: boolean, targetDirty?: boolean): Promise { - let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined); - let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined); - (accessor.textFileService.files).add(sourceModel.resource, sourceModel); - (accessor.textFileService.files).add(targetModel.resource, targetModel); - - await sourceModel.load(); - sourceModel.textEditorModel!.setValue('foo'); - assert.ok(accessor.textFileService.isDirty(sourceModel.resource)); - - if (targetDirty) { - await targetModel.load(); - targetModel.textEditorModel!.setValue('bar'); - assert.ok(accessor.textFileService.isDirty(targetModel.resource)); - } - - if (move) { - await accessor.textFileService.move(sourceModel.resource, targetModel.resource, true); - } else { - await accessor.textFileService.copy(sourceModel.resource, targetModel.resource, true); - } - - assert.equal(targetModel.textEditorModel!.getValue(), 'foo'); - - if (move) { - assert.ok(!accessor.textFileService.isDirty(sourceModel.resource)); - } else { - assert.ok(accessor.textFileService.isDirty(sourceModel.resource)); - } - assert.ok(accessor.textFileService.isDirty(targetModel.resource)); - - sourceModel.dispose(); - targetModel.dispose(); - } }); diff --git a/src/vs/workbench/services/textfile/test/electron-browser/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/electron-browser/textFileService.io.test.ts index d50d6a3bf5e..7be9d0e1c6d 100644 --- a/src/vs/workbench/services/textfile/test/electron-browser/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/electron-browser/textFileService.io.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ITextFileService, snapshotToString, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; @@ -20,57 +21,23 @@ import { generateUuid } from 'vs/base/common/uuid'; import { join, basename } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding'; -import { NativeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { isWindows } from 'vs/base/common/platform'; import { readFileSync, statSync } from 'fs'; import { detectEncodingByBOM } from 'vs/base/test/node/encoding/encoding.test'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; - -class ServiceAccessor { - constructor( - @ITextFileService public textFileService: TestTextFileService - ) { - } -} - -class TestNativeTextFileService extends NativeTextFileService { - - private _testEncoding: TestEncodingOracle | undefined; - get encoding(): TestEncodingOracle { - if (!this._testEncoding) { - this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); - } - - return this._testEncoding; - } -} - -class TestEncodingOracle extends EncodingOracle { - - protected get encodingOverrides(): IEncodingOverride[] { - return [ - { extension: 'utf16le', encoding: UTF16le }, - { extension: 'utf16be', encoding: UTF16be }, - { extension: 'utf8bom', encoding: UTF8_with_bom } - ]; - } - - protected set encodingOverrides(overrides: IEncodingOverride[]) { } -} +import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/electron-browser/workbenchTestServices'; suite('Files - TextFileService i/o', () => { const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'textfileservice'); - let accessor: ServiceAccessor; const disposables = new DisposableStore(); + let service: ITextFileService; let testDir: string; setup(async () => { const instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); const logService = new NullLogService(); const fileService = new FileService(logService); @@ -82,7 +49,7 @@ suite('Files - TextFileService i/o', () => { const collection = new ServiceCollection(); collection.set(IFileService, fileService); - service = instantiationService.createChild(collection).createInstance(TestNativeTextFileService); + service = instantiationService.createChild(collection).createInstance(TestNativeTextFileServiceWithEncodingOverrides); const id = generateUuid(); testDir = join(parentDir, id); @@ -92,7 +59,7 @@ suite('Files - TextFileService i/o', () => { }); teardown(async () => { - (accessor.textFileService.files).dispose(); + (service.files).dispose(); disposables.clear(); @@ -185,7 +152,7 @@ suite('Files - TextFileService i/o', () => { test('create - UTF 8 BOM - empty content - snapshot', async () => { const resource = URI.file(join(testDir, 'small_new.utf8bom')); - await service.create(resource, TextModel.createFromString('').createSnapshot()); + await service.create(resource, createTextModel('').createSnapshot()); assert.equal(await exists(resource.fsPath), true); @@ -196,7 +163,7 @@ suite('Files - TextFileService i/o', () => { test('create - UTF 8 BOM - content provided - snapshot', async () => { const resource = URI.file(join(testDir, 'small_new.utf8bom')); - await service.create(resource, TextModel.createFromString('Hello World').createSnapshot()); + await service.create(resource, createTextModel('Hello World').createSnapshot()); assert.equal(await exists(resource.fsPath), true); @@ -209,7 +176,7 @@ suite('Files - TextFileService i/o', () => { }); test('write - use encoding (UTF 16 BE) - small content as snapshot', async () => { - await testEncoding(URI.file(join(testDir, 'small.txt')), UTF16be, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); + await testEncoding(URI.file(join(testDir, 'small.txt')), UTF16be, createTextModel('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); }); test('write - use encoding (UTF 16 BE) - large content as string', async () => { @@ -217,7 +184,7 @@ suite('Files - TextFileService i/o', () => { }); test('write - use encoding (UTF 16 BE) - large content as snapshot', async () => { - await testEncoding(URI.file(join(testDir, 'lorem.txt')), UTF16be, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); + await testEncoding(URI.file(join(testDir, 'lorem.txt')), UTF16be, createTextModel('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); }); async function testEncoding(resource: URI, encoding: string, content: string | ITextSnapshot, expectedContent: string) { @@ -265,7 +232,7 @@ suite('Files - TextFileService i/o', () => { resolved = await service.readStream(resource, { encoding }); assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content); - await service.write(resource, TextModel.createFromString(content).createSnapshot(), { encoding }); + await service.write(resource, createTextModel(content).createSnapshot(), { encoding }); resolved = await service.readStream(resource, { encoding }); assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content); @@ -287,7 +254,7 @@ suite('Files - TextFileService i/o', () => { const content = (await readFile(resource.fsPath)).toString(); - await service.write(resource, TextModel.createFromString(content).createSnapshot()); + await service.write(resource, createTextModel(content).createSnapshot()); const resolved = await service.readStream(resource); assert.equal(resolved.value.getFirstLineText(999999), content); @@ -308,7 +275,7 @@ suite('Files - TextFileService i/o', () => { const resolved = await service.readStream(resource); assert.equal(resolved.encoding, UTF16le); - await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); + await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, createTextModel('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); }); test('write - UTF8 variations - content as string', async () => { @@ -345,7 +312,7 @@ suite('Files - TextFileService i/o', () => { let detectedEncoding = await detectEncodingByBOM(resource.fsPath); assert.equal(detectedEncoding, null); - const model = TextModel.createFromString((await readFile(resource.fsPath)).toString() + 'updates'); + const model = createTextModel((await readFile(resource.fsPath)).toString() + 'updates'); await service.write(resource, model.createSnapshot(), { encoding: UTF8_with_bom }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); @@ -390,7 +357,7 @@ suite('Files - TextFileService i/o', () => { test('write - ensure BOM in empty file - content as snapshot', async () => { const resource = URI.file(join(testDir, 'small.txt')); - await service.write(resource, TextModel.createFromString('').createSnapshot(), { encoding: UTF8_with_bom }); + await service.write(resource, createTextModel('').createSnapshot(), { encoding: UTF8_with_bom }); let detectedEncoding = await detectEncodingByBOM(resource.fsPath); assert.equal(detectedEncoding, UTF8_with_bom); @@ -413,7 +380,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(result.name, basename(resource.fsPath)); assert.equal(result.size, statSync(resource.fsPath).size); - assert.equal(snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)), snapshotToString(TextModel.createFromString(readFileSync(resource.fsPath).toString()).createSnapshot(false))); + assert.equal(snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)), snapshotToString(createTextModel(readFileSync(resource.fsPath).toString()).createSnapshot(false))); } test('read - small text', async () => { diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index cec284d34d7..bfc6050de83 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -10,13 +10,14 @@ import { ITextModel } from 'vs/editor/common/model'; import { IDisposable, toDisposable, IReference, ReferenceCollection, ImmortalReference } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { ITextFileService, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as network from 'vs/base/common/network'; import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; 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/textmodelResolver/test/browser/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts index a04dad2c3d2..d8ca80f38f4 100644 --- a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts @@ -9,39 +9,24 @@ import { URI } from 'vs/base/common/uri'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -class ServiceAccessor { - constructor( - @ITextModelService public textModelResolverService: ITextModelService, - @IModelService public modelService: IModelService, - @IModeService public modeService: IModeService, - @ITextFileService public textFileService: ITextFileService, - @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService - ) { - } -} - suite('Workbench - TextModelResolverService', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; let model: TextFileEditorModel; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { @@ -115,7 +100,7 @@ suite('Workbench - TextModelResolverService', () => { const input = instantiationService.createInstance(UntitledTextEditorInput, untitledModel); await input.resolve(); - const ref = await accessor.textModelResolverService.createModelReference(input.getResource()); + const ref = await accessor.textModelResolverService.createModelReference(input.resource); const model = ref.object; assert.equal(untitledModel, model); const editorModel = model.textEditorModel; diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index a641d555298..a2ff7a5513e 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); @@ -113,7 +121,6 @@ export class FileIconThemeData implements IFileIconTheme { case 'label': case 'description': case 'settingsId': - case 'extensionData': case 'styleSheetContent': case 'hasFileIcons': case 'hidesExplorerArrows': @@ -124,27 +131,32 @@ export class FileIconThemeData implements IFileIconTheme { case 'location': theme.location = URI.revive(data.location); break; + case 'extensionData': + theme.extensionData = ExtensionData.fromJSONObject(data.extensionData); + break; } } 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, settingsId: this.settingsId, - location: this.location, + location: this.location?.toJSON(), styleSheetContent: this.styleSheetContent, hasFileIcons: this.hasFileIcons, hasFolderIcons: this.hasFolderIcons, hidesExplorerArrows: this.hidesExplorerArrows, + extensionData: ExtensionData.toJSONObject(this.extensionData), watch: this.watch }); + storageService.store(PERSISTED_FILE_ICON_THEME_STORAGE_KEY, data, StorageScope.GLOBAL); } } @@ -228,8 +240,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 +249,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 +262,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 +281,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..3af8cc02c81 --- /dev/null +++ b/src/vs/workbench/services/themes/browser/productIconThemeData.ts @@ -0,0 +1,208 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { 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 'styleSheetContent': + case 'watch': + (theme as any)[key] = data[key]; + break; + case 'location': + theme.location = URI.revive(data.location); + break; + case 'extensionData': + theme.extensionData = ExtensionData.fromJSONObject(data.extensionData); + 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?.toJSON(), + styleSheetContent: this.styleSheetContent, + watch: this.watch, + extensionData: ExtensionData.toJSONObject(this.extensionData), + }); + 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 c2d10bf8442..c8a5a3fd65f 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,179 +99,62 @@ 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(_ => { this.installConfigurationListener(); this.installPreferredSchemeListener(); - }); - - 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(); - } - } - } - }); - - 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; - } - } - - 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.onFileChanges(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(); - } + this.installRegistryListeners(); }); } - 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 +164,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) { @@ -369,6 +230,68 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } + private installRegistryListeners() { + + let prevColorId: string | undefined = undefined; + + // update settings schema setting for theme specific settings + 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.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'); + } + + }); + + 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'); + } + }); + } + + // preferred scheme handling private installPreferredSchemeListener() { @@ -385,11 +308,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 +322,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 +334,29 @@ 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 get onDidColorThemeChange(): Event { + return this.onColorThemeChange.event; } - 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 +365,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 +378,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 +408,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 +420,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 +436,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 +474,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 +536,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 +621,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 +669,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..4b9a05703ed 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,33 @@ 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, - extensionData: this.extensionData, + tokenStylingRules: this.tokenStylingRules.map(TokenStylingRule.toJSONObject), + extensionData: ExtensionData.toJSONObject(this.extensionData), + location: this.location?.toJSON(), + 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 +527,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 +544,26 @@ 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 '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; + case 'location': + theme.location = URI.revive(data.location); + break; + case 'extensionData': + theme.extensionData = ExtensionData.fromJSONObject(data.extensionData); + break; } } if (!theme.id || !theme.settingsId) { @@ -531,56 +605,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 +720,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 +770,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..2ffbd9de7bf 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -6,9 +6,10 @@ 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'; +import { isBoolean, isString } from 'vs/base/common/types'; export const IWorkbenchThemeService = createDecorator('themeService'); @@ -18,19 +19,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 +51,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 +84,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 +93,7 @@ export interface ITokenColorCustomizations { functions?: string | ITokenColorizationSetting; variables?: string | ITokenColorizationSetting; textMateRules?: ITextMateThemingRule[]; + semanticHighlighting?: boolean; } export interface IExperimentalTokenStyleCustomizations { @@ -105,6 +120,18 @@ export interface ExtensionData { extensionLocation: URI; } +export namespace ExtensionData { + export function toJSONObject(d: ExtensionData | undefined): any { + return d && { _extensionId: d.extensionId, _extensionIsBuiltin: d.extensionIsBuiltin, _extensionLocation: d.extensionLocation.toJSON(), _extensionName: d.extensionName, _extensionPublisher: d.extensionPublisher }; + } + export function fromJSONObject(o: any): ExtensionData | undefined { + if (o && isString(o._extensionId) && isBoolean(o._extensionIsBuiltin) && isString(o._extensionLocation) && isString(o._extensionName) && isString(o._extensionPublisher)) { + return { extensionId: o._extensionId, extensionIsBuiltin: o._extensionIsBuiltin, extensionLocation: URI.revive(o._extensionLocation), extensionName: o._extensionName, extensionPublisher: o._extensionPublisher }; + } + return undefined; + } +} + export interface IThemeExtensionPoint { id: string; label?: string; 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 05e918a797d..bd4f8afbc99 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -92,7 +92,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt // Take name from first line if present and only if // we have no associated file path. In that case we // prefer the file name as title. - if (!this.hasAssociatedFilePath && this.cachedModelFirstLineWords) { + if (this.configuredLabelFormat === 'content' && !this.hasAssociatedFilePath && this.cachedModelFirstLineWords) { return this.cachedModelFirstLineWords; } @@ -100,11 +100,13 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return this.labelService.getUriBasenameLabel(this.resource); } - private dirty = false; + private dirty = this.hasAssociatedFilePath || !!this.initialValue; private ignoreDirtyOnModelContentChange = false; private versionId = 0; + private configuredEncoding: string | undefined; + private configuredLabelFormat: 'content' | 'name' = 'content'; constructor( public readonly resource: URI, @@ -130,25 +132,39 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this.setMode(preferredMode); } + // Fetch config + this.onConfigurationChange(false); + this.registerListeners(); } private registerListeners(): void { // Config Changes - this._register(this.textResourceConfigurationService.onDidChangeConfiguration(e => this.onConfigurationChange())); + this._register(this.textResourceConfigurationService.onDidChangeConfiguration(e => this.onConfigurationChange(true))); } - private onConfigurationChange(): void { - const configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); + private onConfigurationChange(fromEvent: boolean): void { + // Encoding + const configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); if (this.configuredEncoding !== configuredEncoding) { this.configuredEncoding = configuredEncoding; - if (!this.preferredEncoding) { + if (fromEvent && !this.preferredEncoding) { this._onDidChangeEncoding.fire(); // do not fire event if we have a preferred encoding set } } + + // Label Format + const configuredLabelFormat = this.textResourceConfigurationService.getValue(this.resource, 'workbench.editor.untitled.labelFormat'); + if (this.configuredLabelFormat !== configuredLabelFormat && (configuredLabelFormat === 'content' || configuredLabelFormat === 'name')) { + this.configuredLabelFormat = configuredLabelFormat; + + if (fromEvent) { + this._onDidChangeName.fire(); + } + } } getVersionId(): number { @@ -237,7 +253,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return !!target; } - async revert(): Promise { + async revert(): Promise { this.setDirty(false); // Emit as event @@ -247,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 { @@ -281,13 +295,10 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this.updateTextEditorModel(undefined, this.preferredMode); } - // Figure out encoding now that model is present - this.configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); - // Listen to text model events const textEditorModel = assertIsDefined(this.textEditorModel); this._register(textEditorModel.onDidChangeContent(e => this.onModelContentChanged(textEditorModel, e))); - this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config + this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange(true))); // mode change can have impact on config // Only adjust name and dirty state etc. if we // actually created the untitled model diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 612132dd170..5c91d4512b6 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -67,22 +67,22 @@ export interface IUntitledTextEditorModelManager { /** * Events for when untitled text editors change (e.g. getting dirty, saved or reverted). */ - readonly onDidChangeDirty: Event; + readonly onDidChangeDirty: Event; /** * Events for when untitled text editor encodings change. */ - readonly onDidChangeEncoding: Event; + readonly onDidChangeEncoding: Event; /** * Events for when untitled text editor labels change. */ - readonly onDidChangeLabel: Event; + readonly onDidChangeLabel: Event; /** * Events for when untitled text editors are disposed. */ - readonly onDidDisposeModel: Event; + readonly onDidDispose: Event; /** * Creates a new untitled editor model with the provided options. If the `untitledResource` @@ -117,16 +117,16 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe _serviceBrand: undefined; - private readonly _onDidChangeDirty = this._register(new Emitter()); + private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onDidChangeEncoding = this._register(new Emitter()); + private readonly _onDidChangeEncoding = this._register(new Emitter()); readonly onDidChangeEncoding = this._onDidChangeEncoding.event; - private readonly _onDidDisposeModel = this._register(new Emitter()); - readonly onDidDisposeModel = this._onDidDisposeModel.event; + private readonly _onDidDispose = this._register(new Emitter()); + readonly onDidDispose = this._onDidDispose.event; - private readonly _onDidChangeLabel = this._register(new Emitter()); + private readonly _onDidChangeLabel = this._register(new Emitter()); readonly onDidChangeLabel = this._onDidChangeLabel.event; private readonly mapResourceToModel = new ResourceMap(); @@ -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.resource))); - modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model.resource))); - modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model.resource))); - modelDisposables.add(model.onDispose(() => this._onDidDisposeModel.fire(model.resource))); + + // 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 011bbabbcd8..bf8c715cb34 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -8,37 +8,22 @@ import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { workbenchInstantiationService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; - -class ServiceAccessor { - constructor( - @IUntitledTextEditorService public readonly untitledTextEditorService: IUntitledTextEditorService, - @IEditorService public readonly editorService: TestEditorService, - @IWorkingCopyService public readonly workingCopyService: IWorkingCopyService, - @IModeService public readonly modeService: ModeServiceImpl, - @IConfigurationService public readonly testConfigurationService: TestConfigurationService - ) { } -} +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; suite('Untitled text editors', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { @@ -51,26 +36,26 @@ suite('Untitled text editors', () => { const input1 = instantiationService.createInstance(UntitledTextEditorInput, service.create()); await input1.resolve(); - assert.equal(service.get(input1.getResource()), input1.model); + assert.equal(service.get(input1.resource), input1.model); - assert.ok(service.get(input1.getResource())); + assert.ok(service.get(input1.resource)); assert.ok(!service.get(URI.file('testing'))); const input2 = instantiationService.createInstance(UntitledTextEditorInput, service.create()); - assert.equal(service.get(input2.getResource()), input2.model); + assert.equal(service.get(input2.resource), input2.model); // get() - assert.equal(service.get(input1.getResource()), input1.model); - assert.equal(service.get(input2.getResource()), input2.model); + assert.equal(service.get(input1.resource), input1.model); + assert.equal(service.get(input2.resource), input2.model); // revert() await input1.revert(0); assert.ok(input1.isDisposed()); - assert.ok(!service.get(input1.getResource())); + assert.ok(!service.get(input1.resource)); // dirty const model = await input2.resolve(); - assert.equal(await service.resolve({ untitledResource: input2.getResource() }), model); + assert.equal(await service.resolve({ untitledResource: input2.resource }), model); assert.ok(service.get(model.resource)); assert.ok(!input2.isDirty()); @@ -81,37 +66,37 @@ suite('Untitled text editors', () => { const resource = await resourcePromise; - assert.equal(resource.toString(), input2.getResource().toString()); + assert.equal(resource.toString(), input2.resource.toString()); assert.ok(input2.isDirty()); - assert.ok(workingCopyService.isDirty(input2.getResource())); + assert.ok(workingCopyService.isDirty(input2.resource)); assert.equal(workingCopyService.dirtyCount, 1); await input1.revert(0); await input2.revert(0); - assert.ok(!service.get(input1.getResource())); - assert.ok(!service.get(input2.getResource())); + assert.ok(!service.get(input1.resource)); + assert.ok(!service.get(input2.resource)); assert.ok(!input2.isDirty()); assert.ok(!model.isDirty()); - assert.ok(!workingCopyService.isDirty(input2.getResource())); + 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.getResource())); + assert.ok(!service.get(input1.resource)); input2.dispose(); - assert.ok(!service.get(input2.getResource())); + assert.ok(!service.get(input2.resource)); }); function awaitDidChangeDirty(service: IUntitledTextEditorService): Promise { return new Promise(c => { - const listener = service.onDidChangeDirty(async resource => { + const listener = service.onDidChangeDirty(async model => { listener.dispose(); - c(resource); + c(model.resource); }); }); } @@ -135,12 +120,24 @@ suite('Untitled text editors', () => { test('associated resource is dirty', async () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); - const untitled = await service.resolve({ associatedResource: file }); - assert.ok(untitled.hasAssociatedFilePath); + 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 resolvedModel = await untitled.resolve(); + + 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 () => { @@ -176,9 +173,9 @@ suite('Untitled text editors', () => { const input = instantiationService.createInstance(UntitledTextEditorInput, service.create()); - const model3 = await instantiationService.createInstance(UntitledTextEditorInput, service.create({ untitledResource: input.getResource() })).resolve(); + const model3 = await instantiationService.createInstance(UntitledTextEditorInput, service.create({ untitledResource: input.resource })).resolve(); - assert.equal(model3.resource.toString(), input.getResource().toString()); + assert.equal(model3.resource.toString(), input.resource.toString()); const file = URI.file(join('C:\\', '/foo/file44.txt')); const model4 = await instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: file })).resolve(); @@ -212,20 +209,14 @@ suite('Untitled text editors', () => { const workingCopyService = accessor.workingCopyService; const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ initialValue: 'Hello World' })); - - let onDidChangeDirty: IWorkingCopy | undefined = undefined; - const listener = workingCopyService.onDidChangeDirty(copy => { - onDidChangeDirty = copy; - }); + assert.ok(untitled.isDirty()); // dirty const model = await untitled.resolve(); assert.ok(model.isDirty()); assert.equal(workingCopyService.dirtyCount, 1); - assert.equal(onDidChangeDirty, model); untitled.dispose(); - listener.dispose(); model.dispose(); }); @@ -328,9 +319,9 @@ suite('Untitled text editors', () => { let counter = 0; - service.onDidChangeEncoding(r => { + service.onDidChangeEncoding(model => { counter++; - assert.equal(r.toString(), input.getResource().toString()); + assert.equal(model.resource.toString(), input.resource.toString()); }); // encoding @@ -347,9 +338,9 @@ suite('Untitled text editors', () => { let counter = 0; - service.onDidChangeLabel(r => { + service.onDidChangeLabel(model => { counter++; - assert.equal(r.toString(), input.getResource().toString()); + assert.equal(model.resource.toString(), input.resource.toString()); }); // label @@ -366,9 +357,9 @@ suite('Untitled text editors', () => { let counter = 0; - service.onDidDisposeModel(r => { + service.onDidDispose(model => { counter++; - assert.equal(r.toString(), input.getResource().toString()); + assert.equal(model.resource.toString(), input.resource.toString()); }); const model = await input.resolve(); 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/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts index 097a3647e24..fd16c9c35d5 100644 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts @@ -348,7 +348,7 @@ suite('FileUserDataProvider - Watching', () => { test('file added change event', done => { const expected = joinPath(userDataResource, 'settings.json'); const target = joinPath(localUserDataResource, 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.ADDED)) { done(); } @@ -362,7 +362,7 @@ suite('FileUserDataProvider - Watching', () => { test('file updated change event', done => { const expected = joinPath(userDataResource, 'settings.json'); const target = joinPath(localUserDataResource, 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.UPDATED)) { done(); } @@ -376,7 +376,7 @@ suite('FileUserDataProvider - Watching', () => { test('file deleted change event', done => { const expected = joinPath(userDataResource, 'settings.json'); const target = joinPath(localUserDataResource, 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.DELETED)) { done(); } @@ -390,7 +390,7 @@ suite('FileUserDataProvider - Watching', () => { test('file under folder created change event', done => { const expected = joinPath(userDataResource, 'snippets', 'settings.json'); const target = joinPath(localUserDataResource, 'snippets', 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.ADDED)) { done(); } @@ -404,7 +404,7 @@ suite('FileUserDataProvider - Watching', () => { test('file under folder updated change event', done => { const expected = joinPath(userDataResource, 'snippets', 'settings.json'); const target = joinPath(localUserDataResource, 'snippets', 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.UPDATED)) { done(); } @@ -418,7 +418,7 @@ suite('FileUserDataProvider - Watching', () => { test('file under folder deleted change event', done => { const expected = joinPath(userDataResource, 'snippets', 'settings.json'); const target = joinPath(localUserDataResource, 'snippets', 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.DELETED)) { done(); } @@ -432,7 +432,7 @@ suite('FileUserDataProvider - Watching', () => { test('event is not triggered if file is not under user data', async () => { const target = joinPath(dirname(localUserDataResource), 'settings.json'); let triggered = false; - testObject.onFileChanges(() => triggered = true); + testObject.onDidFilesChange(() => triggered = true); fileEventEmitter.fire([{ resource: target, type: FileChangeType.DELETED @@ -446,7 +446,7 @@ suite('FileUserDataProvider - Watching', () => { test('backup file created change event', done => { const expected = joinPath(userDataResource, BACKUPS, 'settings.json'); const target = joinPath(localBackupsResource, 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.ADDED)) { done(); } @@ -460,7 +460,7 @@ suite('FileUserDataProvider - Watching', () => { test('backup file update change event', done => { const expected = joinPath(userDataResource, BACKUPS, 'settings.json'); const target = joinPath(localBackupsResource, 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.UPDATED)) { done(); } @@ -474,7 +474,7 @@ suite('FileUserDataProvider - Watching', () => { test('backup file delete change event', done => { const expected = joinPath(userDataResource, BACKUPS, 'settings.json'); const target = joinPath(localBackupsResource, 'settings.json'); - testObject.onFileChanges(e => { + testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.DELETED)) { done(); } diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index c168d05c76a..0fbeb2a56c8 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncUtilService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; import { IStringDictionary } from 'vs/base/common/collections'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -23,7 +23,11 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, ) { } - public async resolveUserBindings(userBindings: string[]): Promise> { + async resolveDefaultIgnoredSettings(): Promise { + return getDefaultIgnoredSettings(); + } + + async resolveUserBindings(userBindings: string[]): Promise> { const keys: IStringDictionary = {}; for (const userbinding of userBindings) { keys[userbinding] = this.keybindingsService.resolveUserBinding(userbinding).map(part => part.getUserSettingsLabel()).join(' '); 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/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts index 7976bb0fbdb..88ee0dcb78f 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataAutoSyncService, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataAutoSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -15,7 +15,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto _serviceBrand: undefined; private readonly channel: IChannel; - get onError(): Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> { return this.channel.listen('onError'); } + get onError(): Event { return Event.map(this.channel.listen('onError'), e => UserDataSyncError.toUserDataSyncError(e)); } constructor( @ISharedProcessService sharedProcessService: ISharedProcessService @@ -24,8 +24,8 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto this.channel = sharedProcessService.getChannel('userDataAutoSync'); } - triggerAutoSync(): Promise { - return this.channel.call('triggerAutoSync'); + triggerAutoSync(sources: string[]): Promise { + return this.channel.call('triggerAutoSync', [sources]); } } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index fe7a877f5ef..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,12 +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<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event; constructor( @ISharedProcessService sharedProcessService: ISharedProcessService @@ -43,12 +52,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return userDataSyncChannel.listen(event, arg); } }; - this.channel.call<[SyncStatus, SyncSource[]]>('_getInitialData').then(([status, conflicts]) => { + this.channel.call<[SyncStatus, SyncResourceConflicts[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => { this.updateStatus(status); this.updateConflicts(conflicts); + if (lastSyncTime) { + this.updateLastSyncTime(lastSyncTime); + } 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('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 { @@ -59,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 { @@ -71,28 +85,55 @@ 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); } + private updateLastSyncTime(lastSyncTime: number): void { + if (this._lastSyncTime !== lastSyncTime) { + this._lastSyncTime = lastSyncTime; + this._onDidChangeLastSyncTime.fire(lastSyncTime); + } + } } registerSingleton(IUserDataSyncService, UserDataSyncService); diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 14366966e16..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); @@ -236,6 +240,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // Register all containers that were registered before this ctor this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); + // Try generating all generated containers that don't need extensions + this.tryGenerateContainers(); + this._register(this.viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(views, viewContainer))); this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(views, viewContainer))); @@ -251,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 { @@ -318,7 +298,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - private onDidRegisterExtensions(): void { + private tryGenerateContainers(fallbackToDefault?: boolean): void { for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { const containerId = containerInfo.containerId; @@ -337,20 +317,28 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - // check if view has been registered to default location - const viewContainer = this.viewsRegistry.getViewContainer(viewId); - const viewDescriptor = this.getViewDescriptor(viewId); - if (viewContainer && viewDescriptor) { - this.addViews(viewContainer, [viewDescriptor]); + if (fallbackToDefault) { + // check if view has been registered to default location + const viewContainer = this.viewsRegistry.getViewContainer(viewId); + const viewDescriptor = this.getViewDescriptor(viewId); + if (viewContainer && viewDescriptor) { + this.addViews(viewContainer, [viewDescriptor]); - const newLocation = this.getViewContainerLocation(viewContainer); - if (containerInfo.location && containerInfo.location !== newLocation) { - this._onDidChangeLocation.fire({ views: [viewDescriptor], from: containerInfo.location, to: newLocation }); + const newLocation = this.getViewContainerLocation(viewContainer); + if (containerInfo.location && containerInfo.location !== newLocation) { + this._onDidChangeLocation.fire({ views: [viewDescriptor], from: containerInfo.location, to: newLocation }); + } } } } - this.saveViewPositionsToCache(); + if (fallbackToDefault) { + this.saveViewPositionsToCache(); + } + } + + private onDidRegisterExtensions(): void { + this.tryGenerateContainers(true); } private onDidRegisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { @@ -366,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 { @@ -434,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); } @@ -475,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 }); } } @@ -485,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); } @@ -537,6 +553,22 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } + // If a value is not present in the cache, it must be reset to default + this.viewContainersRegistry.all.forEach(viewContainer => { + const viewDescriptorCollection = this.getViewDescriptors(viewContainer); + viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { + if (!newCachedPositions.has(viewDescriptor.id)) { + const currentContainer = this.getViewContainer(viewDescriptor.id); + const defaultContainer = this.getDefaultContainer(viewDescriptor.id); + if (currentContainer && defaultContainer && currentContainer !== defaultContainer) { + this.moveViews([viewDescriptor], currentContainer, defaultContainer); + } + + this.cachedViewInfo.delete(viewDescriptor.id); + } + }); + }); + this.cachedViewInfo = this.getCachedViewPositions(); } } @@ -571,6 +603,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor }); }); + // Do no save default positions to the cache + // so that default changes can be recognized + // https://github.com/microsoft/vscode/issues/90414 + for (const [viewId, containerInfo] of this.cachedViewInfo) { + const defaultContainer = this.getDefaultContainer(viewId); + if (defaultContainer?.id === containerInfo.containerId) { + this.cachedViewInfo.delete(viewId); + } + } + this.cachedViewPositionsValue = JSON.stringify([...this.cachedViewInfo]); } @@ -643,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); } @@ -671,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 new file mode 100644 index 00000000000..4b0928035ee --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.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 { localize } from 'vs/nls'; +import { raceTimeout } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IWorkingCopyFileOperationParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +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 { + + private readonly participants: IWorkingCopyFileOperationParticipant[] = []; + + constructor( + @IProgressService private readonly progressService: IProgressService, + @ILogService private readonly logService: ILogService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + } + + addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { + const remove = insert(this.participants, participant); + + return toDisposable(() => remove()); + } + + async participate(target: URI, source: URI | undefined, operation: FileOperation): Promise { + const timeout = this.configurationService.getValue('files.participants.timeout'); + if (timeout <= 0) { + return; // disabled + } + + const cts = new CancellationTokenSource(); + + return this.progressService.withProgress({ + location: ProgressLocation.Window, + title: this.progressLabel(operation) + }, async progress => { + + // For each participant + for (const participant of this.participants) { + if (cts.token.isCancellationRequested) { + break; + } + + try { + const promise = participant.participate(target, source, operation, progress, timeout, cts.token); + await raceTimeout(promise, timeout, () => cts.dispose(true /* cancel */)); + } catch (err) { + this.logService.warn(err); + } + } + }); + } + + private progressLabel(operation: FileOperation): string { + switch (operation) { + case FileOperation.CREATE: + return localize('msg-create', "Running 'File Create' participants..."); + case FileOperation.MOVE: + return localize('msg-rename', "Running 'File Rename' participants..."); + case FileOperation.COPY: + return localize('msg-copy', "Running 'File Copy' participants..."); + case FileOperation.DELETE: + return localize('msg-delete', "Running 'File Delete' participants..."); + } + } + + dispose(): void { + this.participants.splice(0, this.participants.length); + } +} diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts new file mode 100644 index 00000000000..1bfc60d2b8b --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.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 { 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, 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'; +import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; +import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; +import { WorkingCopyFileOperationParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant'; + +export const IWorkingCopyFileService = createDecorator('workingCopyFileService'); + +export interface WorkingCopyFileEvent extends IWaitUntil { + + /** + * An identifier to correlate the operation through the + * different event types (before, after, error). + */ + readonly correlationId: number; + + /** + * The file operation that is taking place. + */ + readonly operation: FileOperation; + + /** + * The resource the event is about. + */ + readonly target: URI; + + /** + * A property that is defined for move operations. + */ + readonly source?: URI; +} + +export interface IWorkingCopyFileOperationParticipant { + + /** + * Participate in a file operation of a working copy. Allows to + * change the working copy before it is being saved to disk. + */ + participate( + target: URI, + source: URI | undefined, + operation: FileOperation, + progress: IProgress, + timeout: number, + token: CancellationToken + ): 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 + * sure to revert the working copy first. + * + * On top of that events are provided to participate in each state of the + * operation to perform additional work. + */ +export interface IWorkingCopyFileService { + + _serviceBrand: undefined; + + //#region Events + + /** + * An event that is fired when a certain working copy IO operation is about to run. + * + * Participants can join this event with a long running operation to keep some state + * before the operation is started, but working copies should not be changed at this + * point in time. For that purpose, use the `IWorkingCopyFileOperationParticipant` API. + */ + readonly onWillRunWorkingCopyFileOperation: Event; + + /** + * An event that is fired after a working copy IO operation has failed. + * + * Participants can join this event with a long running operation to clean up as needed. + */ + readonly onDidFailWorkingCopyFileOperation: Event; + + /** + * An event that is fired after a working copy IO operation has been performed. + * + * Participants can join this event with a long running operation to make changes + * after the operation has finished. + */ + readonly onDidRunWorkingCopyFileOperation: Event; + + //#endregion + + + //#region File operation participants + + /** + * Adds a participant for file operations on working copies. + */ + addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable; + + /** + * Execute all known file operation participants. + */ + runFileOperationParticipants(target: URI, source: URI | undefined, operation: FileOperation): Promise + + + //#region File operations + + /** + * Will move working copies matching the provided resource and children + * to the target resource using the associated file service for that resource. + * + * Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and + * `onDidRunWorkingCopyFileOperation` events to participate. + */ + move(source: URI, target: URI, overwrite?: boolean): Promise; + + /** + * Will copy working copies matching the provided resource and children + * to the target using the associated file service for that resource. + * + * Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and + * `onDidRunWorkingCopyFileOperation` events to participate. + */ + copy(source: URI, target: URI, overwrite?: boolean): Promise; + + /** + * Will delete working copies matching the provided resource and children + * using the associated file service for that resource. + * + * Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and + * `onDidRunWorkingCopyFileOperation` events to participate. + */ + delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise; + + //#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 + * copy that is dirty and is a child of that folder will also be returned. + */ + getDirty(resource: URI): IWorkingCopy[]; + + //#endregion +} + +export class WorkingCopyFileService extends Disposable implements IWorkingCopyFileService { + + _serviceBrand: undefined; + + //#region Events + + private readonly _onWillRunWorkingCopyFileOperation = this._register(new AsyncEmitter()); + readonly onWillRunWorkingCopyFileOperation = this._onWillRunWorkingCopyFileOperation.event; + + private readonly _onDidFailWorkingCopyFileOperation = this._register(new AsyncEmitter()); + readonly onDidFailWorkingCopyFileOperation = this._onDidFailWorkingCopyFileOperation.event; + + private readonly _onDidRunWorkingCopyFileOperation = this._register(new AsyncEmitter()); + readonly onDidRunWorkingCopyFileOperation = this._onDidRunWorkingCopyFileOperation.event; + + //#endregion + + private correlationIds = 0; + + constructor( + @IFileService private readonly fileService: IFileService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @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 { + return this.moveOrCopy(source, target, true, overwrite); + } + + async copy(source: URI, target: URI, overwrite?: boolean): Promise { + return this.moveOrCopy(source, target, false, overwrite); + } + + private async moveOrCopy(source: URI, target: URI, move: boolean, overwrite?: boolean): Promise { + + // file operation participant + await this.runFileOperationParticipants(target, source, move ? FileOperation.MOVE : FileOperation.COPY); + + // before event + const event = { correlationId: this.correlationIds++, operation: move ? FileOperation.MOVE : FileOperation.COPY, target, source }; + await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + + // handle dirty working copies depending on the operation: + // - move: revert both source and target (if any) + // - copy: revert target (if any) + const dirtyWorkingCopies = (move ? [...this.getDirty(source), ...this.getDirty(target)] : this.getDirty(target)); + await Promise.all(dirtyWorkingCopies.map(dirtyWorkingCopy => dirtyWorkingCopy.revert({ soft: true }))); + + // now we can rename the source to target via file operation + let stat: IFileStatWithMetadata; + try { + if (move) { + stat = await this.fileService.move(source, target, overwrite); + } else { + stat = await this.fileService.copy(source, target, overwrite); + } + } catch (error) { + + // error event + await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + + throw error; + } + + // after event + await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + + return stat; + } + + async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { + + // file operation participant + await this.runFileOperationParticipants(resource, undefined, FileOperation.DELETE); + + // before events + const event = { correlationId: this.correlationIds++, operation: FileOperation.DELETE, target: resource }; + await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + + // Check for any existing dirty working copies for the resource + // and do a soft revert before deleting to be able to close + // any opened editor with these working copies + const dirtyWorkingCopies = this.getDirty(resource); + await Promise.all(dirtyWorkingCopies.map(dirtyWorkingCopy => dirtyWorkingCopy.revert({ soft: true }))); + + // Now actually delete from disk + try { + await this.fileService.del(resource, options); + } catch (error) { + + // error event + await this._onDidFailWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + + throw error; + } + + // after event + await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + } + + + //#region File operation participants + + private readonly fileOperationParticipants = this._register(this.instantiationService.createInstance(WorkingCopyFileOperationParticipant)); + + addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { + return this.fileOperationParticipants.addFileOperationParticipant(participant); + } + + runFileOperationParticipants(target: URI, source: URI | undefined, operation: FileOperation): Promise { + return this.fileOperationParticipants.participate(target, source, operation); + } + + //#endregion + + + //#region Path related + + private readonly workingCopyProviders: WorkingCopyProvider[] = []; + + 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 +} + +registerSingleton(IWorkingCopyFileService, WorkingCopyFileService, true); 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 new file mode 100644 index 00000000000..d533349c9ae --- /dev/null +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -0,0 +1,215 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +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', () => { + + let instantiationService: IInstantiationService; + let model: TextFileEditorModel; + let accessor: TestServiceAccessor; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(TestServiceAccessor); + }); + + teardown(() => { + model?.dispose(); + (accessor.textFileService.files).dispose(); + }); + + test('delete - dirty file', async function () { + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); + (accessor.textFileService.files).add(model.resource, model); + + await model.load(); + model!.textEditorModel!.setValue('foo'); + assert.ok(accessor.workingCopyService.isDirty(model.resource)); + + let eventCounter = 0; + let correlationId: number | undefined = undefined; + + const participant = accessor.workingCopyFileService.addFileOperationParticipant({ + participate: async (target, source, operation) => { + assert.equal(target.toString(), model.resource.toString()); + assert.equal(operation, FileOperation.DELETE); + eventCounter++; + } + }); + + const listener1 = accessor.workingCopyFileService.onWillRunWorkingCopyFileOperation(e => { + assert.equal(e.target.toString(), model.resource.toString()); + assert.equal(e.operation, FileOperation.DELETE); + correlationId = e.correlationId; + eventCounter++; + }); + + const listener2 = accessor.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => { + assert.equal(e.target.toString(), model.resource.toString()); + assert.equal(e.operation, FileOperation.DELETE); + assert.equal(e.correlationId, correlationId); + eventCounter++; + }); + + await accessor.workingCopyFileService.delete(model.resource); + assert.ok(!accessor.workingCopyService.isDirty(model.resource)); + + assert.equal(eventCounter, 3); + + participant.dispose(); + listener1.dispose(); + listener2.dispose(); + }); + + test('move - dirty file', async function () { + await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true); + }); + + test('move - dirty file (target exists and is dirty)', async function () { + await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true, true); + }); + + test('copy - dirty file', async function () { + await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), false); + }); + + test('copy - dirty file (target exists and is dirty)', async function () { + await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), false, true); + }); + + async function testMoveOrCopy(source: URI, target: URI, move: boolean, targetDirty?: boolean): Promise { + let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined); + let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined); + (accessor.textFileService.files).add(sourceModel.resource, sourceModel); + (accessor.textFileService.files).add(targetModel.resource, targetModel); + + await sourceModel.load(); + sourceModel.textEditorModel!.setValue('foo'); + assert.ok(accessor.textFileService.isDirty(sourceModel.resource)); + + if (targetDirty) { + await targetModel.load(); + targetModel.textEditorModel!.setValue('bar'); + assert.ok(accessor.textFileService.isDirty(targetModel.resource)); + } + + let eventCounter = 0; + let correlationId: number | undefined = undefined; + + const participant = accessor.workingCopyFileService.addFileOperationParticipant({ + participate: async (target, source, operation) => { + assert.equal(target.toString(), targetModel.resource.toString()); + assert.equal(source?.toString(), sourceModel.resource.toString()); + assert.equal(operation, move ? FileOperation.MOVE : FileOperation.COPY); + eventCounter++; + } + }); + + const listener1 = accessor.workingCopyFileService.onWillRunWorkingCopyFileOperation(e => { + assert.equal(e.target.toString(), targetModel.resource.toString()); + assert.equal(e.source?.toString(), sourceModel.resource.toString()); + assert.equal(e.operation, move ? FileOperation.MOVE : FileOperation.COPY); + eventCounter++; + correlationId = e.correlationId; + }); + + const listener2 = accessor.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => { + assert.equal(e.target.toString(), targetModel.resource.toString()); + assert.equal(e.source?.toString(), sourceModel.resource.toString()); + assert.equal(e.operation, move ? FileOperation.MOVE : FileOperation.COPY); + eventCounter++; + assert.equal(e.correlationId, correlationId); + }); + + if (move) { + await accessor.workingCopyFileService.move(sourceModel.resource, targetModel.resource, true); + } else { + await accessor.workingCopyFileService.copy(sourceModel.resource, targetModel.resource, true); + } + + assert.equal(targetModel.textEditorModel!.getValue(), 'foo'); + + if (move) { + assert.ok(!accessor.textFileService.isDirty(sourceModel.resource)); + } else { + assert.ok(accessor.textFileService.isDirty(sourceModel.resource)); + } + assert.ok(accessor.textFileService.isDirty(targetModel.resource)); + + assert.equal(eventCounter, 3); + + sourceModel.dispose(); + targetModel.dispose(); + + participant.dispose(); + listener1.dispose(); + listener2.dispose(); + } + + test('getDirty', async function () { + const model1 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-1.txt'), 'utf8', undefined); + (accessor.textFileService.files).add(model.resource, model); + + const model2 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-2.txt'), 'utf8', undefined); + (accessor.textFileService.files).add(model.resource, model); + + let dirty = accessor.workingCopyFileService.getDirty(model1.resource); + assert.equal(dirty.length, 0); + + await model1.load(); + model1.textEditorModel!.setValue('foo'); + + dirty = accessor.workingCopyFileService.getDirty(model1.resource); + assert.equal(dirty.length, 1); + assert.equal(dirty[0], model1); + + dirty = accessor.workingCopyFileService.getDirty(toResource.call(this, '/path')); + assert.equal(dirty.length, 1); + assert.equal(dirty[0], model1); + + await model2.load(); + model2.textEditorModel!.setValue('bar'); + + dirty = accessor.workingCopyFileService.getDirty(toResource.call(this, '/path')); + assert.equal(dirty.length, 2); + + 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..c48f999da64 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'; @@ -23,6 +23,7 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { Schemas } from 'vs/base/common/network'; export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditingService { @@ -127,13 +128,10 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi private async doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise { const state = this.contextService.getWorkbenchState(); - if (this.environmentService.configuration.remoteAuthority) { - - // Do not allow workspace folders with scheme different than the current remote scheme - const schemas = this.contextService.getWorkspace().folders.map(f => f.uri.scheme); - if (schemas.length && foldersToAdd.some(f => schemas.indexOf(f.uri.scheme) === -1)) { - throw new Error(nls.localize('differentSchemeRoots', "Workspace folders from different providers are not allowed in the same workspace.")); - } + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + if (remoteAuthority) { + // https://github.com/microsoft/vscode/issues/94191 + foldersToAdd = foldersToAdd.filter(f => f.uri.scheme !== Schemas.file && (f.uri.scheme !== Schemas.vscodeRemote || f.uri.authority === remoteAuthority)); } // If we are in no-workspace or single-folder workspace, adding folders has to @@ -234,9 +232,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 523742542a2..768e9528a37 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { setUnexpectedErrorHandler, errorHandler } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { TextModel as EditorModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { TestRPCProtocol } from './testRPCProtocol'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { IMarkerService } from 'vs/platform/markers/common/markers'; @@ -41,15 +41,15 @@ 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'; const defaultSelector = { scheme: 'far' }; -const model: ITextModel = EditorModel.createFromString( +const model: ITextModel = createTextModel( [ 'This is the first line', 'This is the second line', @@ -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 1471cd6e4ef..02c2209624b 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -8,7 +8,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { setUnexpectedErrorHandler, errorHandler } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { TextModel as EditorModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { Position as EditorPosition, Position } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; import { TestRPCProtocol } from './testRPCProtocol'; @@ -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'; @@ -48,9 +48,10 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ import { dispose } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { Progress } from 'vs/platform/progress/common/progress'; const defaultSelector = { scheme: 'far' }; -const model: ITextModel = EditorModel.createFromString( +const model: ITextModel = createTextModel( [ 'This is the first line', 'This is the second line', @@ -590,7 +591,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 2); const [first, second] = actions; assert.equal(first.title, 'Testing1'); @@ -614,7 +615,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); const [first] = actions; assert.equal(first.title, 'Testing1'); @@ -637,7 +638,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); }); @@ -655,7 +656,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); }); 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/extHostWebview.test.ts b/src/vs/workbench/test/browser/api/extHostWebview.test.ts index f08947b6b03..c3e830dd4b9 100644 --- a/src/vs/workbench/test/browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/browser/api/extHostWebview.test.ts @@ -13,18 +13,33 @@ import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { mock } from 'vs/workbench/test/browser/api/mock'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; +import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; suite('ExtHostWebview', () => { + let rpcProtocol: (IExtHostRpcService & IExtHostContext) | undefined; + let extHostDocuments: ExtHostDocuments | undefined; + + setup(() => { + const shape = createNoopMainThreadWebviews(); + rpcProtocol = SingleProxyRPCProtocol(shape); + + const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); + extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); + }); + test('Cannot register multiple serializers for the same view type', async () => { const viewType = 'view.type'; - const shape = createNoopMainThreadWebviews(); - const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { + const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { webviewCspSource: '', webviewResourceRoot: '', isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService, extHostDocuments!); let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined; @@ -57,12 +72,11 @@ suite('ExtHostWebview', () => { }); test('asWebviewUri for desktop vscode-resource scheme', () => { - const shape = createNoopMainThreadWebviews(); - const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { + const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { webviewCspSource: '', webviewResourceRoot: 'vscode-resource://{{resource}}', isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService, extHostDocuments!); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( @@ -97,13 +111,11 @@ suite('ExtHostWebview', () => { }); test('asWebviewUri for web endpoint', () => { - const shape = createNoopMainThreadWebviews(); - - const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { + const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { webviewCspSource: '', webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`, isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService, extHostDocuments!); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); function stripEndpointUuid(input: string) { diff --git a/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts index deae2eab0fe..bd1c55ebce7 100644 --- a/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts @@ -20,6 +20,7 @@ import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IPatternInfo } from 'vs/workbench/services/search/common/search'; +import { isWindows } from 'vs/base/common/platform'; function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace { const result = new ExtHostWorkspace( @@ -552,14 +553,17 @@ suite('ExtHostWorkspace', function () { }); test('`vscode.workspace.getWorkspaceFolder(file)` don\'t return workspace folder when file open from command line. #36221', function () { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { - id: 'foo', name: 'Test', folders: [ - aWorkspaceFolderData(URI.file('c:/Users/marek/Desktop/vsc_test/'), 0) - ] - }, new NullLogService()); + if (isWindows) { - assert.ok(ws.getWorkspaceFolder(URI.file('c:/Users/marek/Desktop/vsc_test/a.txt'))); - assert.ok(ws.getWorkspaceFolder(URI.file('C:/Users/marek/Desktop/vsc_test/b.txt'))); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { + id: 'foo', name: 'Test', folders: [ + aWorkspaceFolderData(URI.file('c:/Users/marek/Desktop/vsc_test/'), 0) + ] + }, new NullLogService()); + + assert.ok(ws.getWorkspaceFolder(URI.file('c:/Users/marek/Desktop/vsc_test/a.txt'))); + assert.ok(ws.getWorkspaceFolder(URI.file('C:/Users/marek/Desktop/vsc_test/b.txt'))); + } }); function aWorkspaceFolderData(uri: URI, index: number, name: string = ''): IWorkspaceFolderData { diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts index dfef5fee0f5..234f1163ae4 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { MainThreadDocumentContentProviders } from 'vs/workbench/api/browser/mainThreadDocumentContentProviders'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { mock } from 'vs/workbench/test/browser/api/mock'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; @@ -18,7 +18,7 @@ suite('MainThreadDocumentContentProviders', function () { test('events are processed properly', function () { let uri = URI.parse('test:uri'); - let model = TextModel.createFromString('1', undefined, undefined, uri); + let model = createTextModel('1', undefined, undefined, uri); let providers = new MainThreadDocumentContentProviders(new TestRPCProtocol(), null!, null!, new class extends mock() { diff --git a/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts index 8ec19753623..a14eca8b02a 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { timeout } from 'vs/base/common/async'; suite('BoundModelReferenceCollection', () => { @@ -21,7 +21,7 @@ suite('BoundModelReferenceCollection', () => { let didDispose = false; col.add({ - object: { textEditorModel: TextModel.createFromString('farboo') }, + object: { textEditorModel: createTextModel('farboo') }, dispose() { didDispose = true; } @@ -36,20 +36,20 @@ suite('BoundModelReferenceCollection', () => { let disposed: number[] = []; col.add({ - object: { textEditorModel: TextModel.createFromString('farboo') }, + object: { textEditorModel: createTextModel('farboo') }, dispose() { disposed.push(0); } }); col.add({ - object: { textEditorModel: TextModel.createFromString('boofar') }, + object: { textEditorModel: createTextModel('boofar') }, dispose() { disposed.push(1); } }); col.add({ - object: { textEditorModel: TextModel.createFromString(new Array(71).join('x')) }, + object: { textEditorModel: createTextModel(new Array(71).join('x')) }, dispose() { disposed.push(2); } diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index 2f7c6a1f2b6..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'; @@ -22,6 +22,10 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; 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 { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; suite('MainThreadDocumentsAndEditors', () => { @@ -44,7 +48,10 @@ suite('MainThreadDocumentsAndEditors', () => { deltas.length = 0; const configService = new TestConfigurationService(); configService.setUserConfiguration('editor', { 'detectIndentation': false }); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService()); + 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, dialogService); codeEditorService = new TestCodeEditorService(); textFileService = new class extends mock() { isDirty() { return false; } @@ -58,7 +65,7 @@ suite('MainThreadDocumentsAndEditors', () => { const editorGroupService = new TestEditorGroupsService(); const fileService = new class extends mock() { - onAfterOperation = Event.None; + onDidRunOperation = Event.None; }; new MainThreadDocumentsAndEditors( diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index dedc04cf8d3..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'; @@ -40,6 +40,14 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('MainThreadEditors', () => { @@ -62,7 +70,10 @@ suite('MainThreadEditors', () => { const configService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService()); + 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, dialogService); const services = new ServiceCollection(); @@ -72,6 +83,9 @@ suite('MainThreadEditors', () => { services.set(IWorkspaceContextService, new TestContextService()); services.set(IWorkbenchEnvironmentService, TestEnvironmentService); services.set(IConfigurationService, configService); + services.set(IDialogService, dialogService); + services.set(INotificationService, notificationService); + services.set(IUndoRedoService, undoRedoService); services.set(IModelService, modelService); services.set(ICodeEditorService, new TestCodeEditorService()); services.set(IFileService, new TestFileService()); @@ -79,14 +93,17 @@ suite('MainThreadEditors', () => { services.set(IEditorGroupsService, new TestEditorGroupsService()); services.set(ITextFileService, new class extends mock() { isDirty() { return false; } - create(uri: URI, contents?: string, options?: any) { - createdResources.add(uri); + create(resource: URI) { + createdResources.add(resource); return Promise.resolve(Object.create(null)); } - delete(resource: URI) { - deletedResources.add(resource); - return Promise.resolve(undefined); - } + files = { + onDidSave: Event.None, + onDidRevert: Event.None, + onDidChangeDirty: Event.None + }; + }); + services.set(IWorkingCopyFileService, new class extends mock() { move(source: URI, target: URI) { movedResources.set(source, target); return Promise.resolve(Object.create(null)); @@ -95,11 +112,10 @@ suite('MainThreadEditors', () => { copiedResources.set(source, target); return Promise.resolve(Object.create(null)); } - files = { - onDidSave: Event.None, - onDidRevert: Event.None, - onDidChangeDirty: Event.None - }; + delete(resource: URI) { + deletedResources.add(resource); + return Promise.resolve(undefined); + } }); services.set(ITextModelService, new class extends mock() { createModelReference(resource: URI): Promise> { 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 2bee5d76faa..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(); @@ -64,6 +65,9 @@ class MyInputFactory implements IEditorInputFactory { } class MyInput extends EditorInput { + + readonly resource = undefined; + getPreferredEditorId(ids: string[]) { return ids[1]; } @@ -78,6 +82,9 @@ class MyInput extends EditorInput { } class MyOtherInput extends EditorInput { + + readonly resource = undefined; + getTypeId(): string { return ''; } @@ -86,7 +93,7 @@ class MyOtherInput extends EditorInput { return null; } } -class MyResourceInput extends ResourceEditorInput { } +class MyResourceEditorInput extends ResourceEditorInput { } suite('Workbench base editor', () => { @@ -97,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()); @@ -114,7 +119,6 @@ suite('Workbench base editor', () => { e.setVisible(false, group); assert(!e.isVisible()); assert(!e.input); - assert(!e.options); assert(!e.getControl()); }); @@ -150,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); @@ -166,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()); }); @@ -248,6 +252,37 @@ suite('Workbench base editor', () => { assert.ok(!memento.loadEditorState(testGroup0, URI.file('/E'))); }); + test('EditorMemento - move', function () { + const testGroup0 = new TestEditorGroupView(0); + + const editorGroupService = new TestEditorGroupsService([testGroup0]); + + interface TestViewState { line: number; } + + const rawMemento = Object.create(null); + let memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService); + + memento.saveEditorState(testGroup0, URI.file('/some/folder/file-1.txt'), { line: 1 }); + memento.saveEditorState(testGroup0, URI.file('/some/folder/file-2.txt'), { line: 2 }); + memento.saveEditorState(testGroup0, URI.file('/some/other/file.txt'), { line: 3 }); + + memento.moveEditorState(URI.file('/some/folder/file-1.txt'), URI.file('/some/folder/file-moved.txt')); + + let res = memento.loadEditorState(testGroup0, URI.file('/some/folder/file-1.txt')); + assert.ok(!res); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder/file-moved.txt')); + assert.equal(res?.line, 1); + + memento.moveEditorState(URI.file('/some/folder'), URI.file('/some/folder-moved')); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder-moved/file-moved.txt')); + assert.equal(res?.line, 1); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder-moved/file-2.txt')); + assert.equal(res?.line, 2); + }); + test('EditoMemento - use with editor input', function () { const testGroup0 = new TestEditorGroupView(0); @@ -256,7 +291,7 @@ suite('Workbench base editor', () => { } class TestEditorInput extends EditorInput { - constructor(private resource: URI, private id = 'testEditorInputForMementoTest') { + constructor(public resource: URI, private id = 'testEditorInputForMementoTest') { super(); } getTypeId() { return 'testEditorInputForMementoTest'; } @@ -265,10 +300,6 @@ suite('Workbench base editor', () => { matches(other: TestEditorInput): boolean { return other && this.id === other.id && other instanceof TestEditorInput; } - - getResource(): URI { - return this.resource; - } } const rawMemento = Object.create(null); 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 366a03fdfd6..e974da14e70 100644 --- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts @@ -4,47 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; -import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, TestEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; import { Schemas } from 'vs/base/common/network'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -class ServiceAccessor { - constructor(@IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService) { } -} - -class FileEditorInput extends EditorInput { - - constructor(private resource: URI) { - super(); - } - - getTypeId(): string { - return 'editorResourceFileTest'; - } - - getResource(): URI { - return this.resource; - } - - resolve(): Promise { - return Promise.resolve(null); - } -} - suite('Workbench editor', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { @@ -58,18 +33,22 @@ suite('Workbench editor', () => { const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create()); - assert.equal(toResource(untitled)!.toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.getResource().toString()); + 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 })); - const file = new FileEditorInput(URI.file('/some/path.txt')); + const file = new TestEditorInput(URI.file('/some/path.txt'), 'editorResourceFileTest'); - assert.equal(toResource(file)!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { filterByScheme: Schemas.file })!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); + 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 })); const diffEditorInput = new DiffEditorInput('name', 'description', untitled, file); @@ -77,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.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().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/editorDiffModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts index 369db0b1abb..37bfc6dc478 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts @@ -6,31 +6,20 @@ import * as assert from 'assert'; import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { URI } from 'vs/base/common/uri'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -class ServiceAccessor { - constructor( - @ITextModelService public textModelResolverService: ITextModelService, - @IModelService public modelService: IModelService, - @IModeService public modeService: IModeService, - ) { - } -} - suite('Workbench editor model', () => { + let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('TextDiffEditorModel', async () => { 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 d70d7fc2709..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(); @@ -73,6 +74,9 @@ function groupListener(group: EditorGroup): GroupEvents { let index = 0; class TestEditorInput extends EditorInput { + + readonly resource = undefined; + constructor(public id: string) { super(); } @@ -93,6 +97,9 @@ class TestEditorInput extends EditorInput { } class NonSerializableTestEditorInput extends EditorInput { + + readonly resource = undefined; + constructor(public id: string) { super(); } @@ -106,7 +113,7 @@ class NonSerializableTestEditorInput extends EditorInput { class TestFileEditorInput extends EditorInput implements IFileEditorInput { - constructor(public id: string, private resource: URI) { + constructor(public id: string, public resource: URI) { super(); } getTypeId() { return 'testFileEditorInputForGroups'; } @@ -114,10 +121,10 @@ class TestFileEditorInput extends EditorInput implements IFileEditorInput { setEncoding(encoding: string) { } getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } - getResource(): URI { return this.resource; } setForceOpenAsBinary(): void { } setMode(mode: string) { } setPreferredMode(mode: string) { } + isResolved(): boolean { return false; } matches(other: TestFileEditorInput): boolean { return other && this.id === other.id && other instanceof TestFileEditorInput; diff --git a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts index 3adc2957bf5..8e00169fc0c 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts @@ -8,6 +8,8 @@ import { EditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; class MyEditorInput extends EditorInput { + readonly resource = undefined; + getTypeId(): string { return ''; } resolve(): any { return null; } } 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 a754831a0a1..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,7 +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 { @@ -70,8 +78,15 @@ suite('Workbench editor model', () => { }); function stubModelService(instantiationService: TestInstantiationService): IModelService { + const dialogService = new TestDialogService(); + const notificationService = new TestNotificationService(); + const undoRedoService = new UndoRedoService(dialogService, notificationService); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); + 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 c610a4ddbfb..224d20cfa21 100644 --- a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts @@ -21,6 +21,9 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands'; 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', () => { @@ -40,8 +43,8 @@ suite('Editor - Range decorations', () => { model = aModel(URI.file('some_file')); codeEditor = createTestCodeEditor({ model: model }); - instantiationService.stub(IEditorService, 'activeEditor', { getResource: () => { return codeEditor.getModel()!.uri; } }); - instantiationService.stub(IEditorService, 'activeTextEditorWidget', codeEditor); + instantiationService.stub(IEditorService, 'activeEditor', { get resource() { return codeEditor.getModel()!.uri; } }); + instantiationService.stub(IEditorService, 'activeTextEditorControl', codeEditor); testObject = instantiationService.createInstance(RangeHighlightDecorations); }); @@ -136,7 +139,7 @@ suite('Editor - Range decorations', () => { } function aModel(resource: URI, content: string = text): TextModel { - let model = TextModel.createFromString(content, TextModel.DEFAULT_CREATION_OPTIONS, null, resource); + let model = createTextModel(content, TextModel.DEFAULT_CREATION_OPTIONS, null, resource); modelsToDispose.push(model); return model; } @@ -156,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/editor/resourceEditorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts index 2eaede4adbc..7634e777a07 100644 --- a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts @@ -8,26 +8,18 @@ import { URI } from 'vs/base/common/uri'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -class ServiceAccessor { - constructor( - @IModelService public modelService: IModelService, - @IModeService public modeService: IModeService - ) { } -} - suite('Resource text editors', () => { + let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('basics', async () => { 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 67ef5397a99..cae22fd1880 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -10,16 +10,15 @@ 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 } 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 Severity from 'vs/base/common/severity'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; 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 } 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'; @@ -43,32 +42,30 @@ import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/pos import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; +import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, ITextSnapshot } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; -import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +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 } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +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, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditorInputType, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; +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'; 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 } from 'vs/platform/storage/common/storage'; -import { isLinux } from 'vs/base/common/platform'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +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'; @@ -84,18 +81,33 @@ 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'; import { Direction } from 'vs/base/browser/ui/grid/grid'; +import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, Progress } from 'vs/platform/progress/common/progress'; +import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { CancellationToken } from 'vs/base/common/cancellation'; +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'; +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 import TestTextResourcePropertiesService = CommonWorkbenchTestServices.TestTextResourcePropertiesService; -export import TestContextService = CommonWorkbenchTestServices.TestContextService; -export import TestStorageService = CommonWorkbenchTestServices.TestStorageService; -export import TestWorkingCopyService = CommonWorkbenchTestServices.TestWorkingCopyService; - -export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { +export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); } @@ -103,6 +115,88 @@ export interface ITestInstantiationService extends IInstantiationService { stub(service: ServiceIdentifier, ctor: any): T; } +export function workbenchInstantiationService(overrides?: { textFileService?: (instantiationService: IInstantiationService) => ITextFileService }): ITestInstantiationService { + const instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); + + instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); + instantiationService.stub(IEnvironmentService, TestEnvironmentService); + const contextKeyService = instantiationService.createInstance(MockContextKeyService); + instantiationService.stub(IContextKeyService, contextKeyService); + instantiationService.stub(IProgressService, new TestProgressService()); + const workspaceContextService = new TestContextService(TestWorkspace); + instantiationService.stub(IWorkspaceContextService, workspaceContextService); + const configService = new TestConfigurationService(); + instantiationService.stub(IConfigurationService, configService); + 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(IRemotePathService, new TestRemotePathService(TestEnvironmentService)); + const layoutService = new TestLayoutService(); + instantiationService.stub(IWorkbenchLayoutService, layoutService); + instantiationService.stub(IDialogService, new TestDialogService()); + 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()); + instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(INotificationService, new TestNotificationService()); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); + instantiationService.stub(IMenuService, new TestMenuService()); + 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)); + instantiationService.stub(ILogService, new NullLogService()); + const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); + instantiationService.stub(IEditorGroupsService, editorGroupService); + instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); + const editorService = new TestEditorService(editorGroupService); + 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(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService)); + instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService()); + + return instantiationService; +} + +export class TestServiceAccessor { + constructor( + @ILifecycleService public lifecycleService: TestLifecycleService, + @ITextFileService public textFileService: TestTextFileService, + @IWorkingCopyFileService public workingCopyFileService: IWorkingCopyFileService, + @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, + @IWorkspaceContextService public contextService: TestContextService, + @IModelService public modelService: ModelServiceImpl, + @IFileService public fileService: TestFileService, + @IFileDialogService public fileDialogService: TestFileDialogService, + @IWorkingCopyService public workingCopyService: IWorkingCopyService, + @IEditorService public editorService: TestEditorService, + @IEditorGroupsService public editorGroupService: IEditorGroupsService, + @IModeService public modeService: IModeService, + @ITextModelService public textModelResolverService: ITextModelService, + @IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService, + @IConfigurationService public testConfigurationService: TestConfigurationService, + @IBackupFileService public backupFileService: TestBackupFileService, + @IHostService public hostService: TestHostService, + @IQuickInputService public quickInputService: IQuickInputService + ) { } +} + export class TestTextFileService extends BrowserTextFileService { private resolveTextContentError!: FileOperationError | null; @@ -120,8 +214,8 @@ export class TestTextFileService extends BrowserTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @INotificationService notificationService: INotificationService, - @IRemotePathService remotePathService: IRemotePathService + @IRemotePathService remotePathService: IRemotePathService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService ) { super( fileService, @@ -136,8 +230,8 @@ export class TestTextFileService extends BrowserTextFileService { filesConfigurationService, textModelService, codeEditorService, - notificationService, - remotePathService + remotePathService, + workingCopyFileService ); } @@ -167,54 +261,23 @@ export class TestTextFileService extends BrowserTextFileService { } } -export const TestEnvironmentService = new BrowserWorkbenchEnvironmentService(Object.create(null)); +class TestEnvironmentServiceWithArgs extends BrowserWorkbenchEnvironmentService { + args = []; +} -export function workbenchInstantiationService(overrides?: { textFileService?: (instantiationService: IInstantiationService) => ITextFileService }): ITestInstantiationService { - const instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); +export const TestEnvironmentService = new TestEnvironmentServiceWithArgs(Object.create(null)); - instantiationService.stub(IEnvironmentService, TestEnvironmentService); - const contextKeyService = instantiationService.createInstance(MockContextKeyService); - instantiationService.stub(IContextKeyService, contextKeyService); - const workspaceContextService = new TestContextService(TestWorkspace); - instantiationService.stub(IWorkspaceContextService, workspaceContextService); - const configService = new TestConfigurationService(); - instantiationService.stub(IConfigurationService, configService); - instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); - instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); - instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); - instantiationService.stub(IDialogService, new TestDialogService()); - instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); - instantiationService.stub(IFileDialogService, new TestFileDialogService()); - instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); - instantiationService.stub(IHistoryService, new TestHistoryService()); - instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); - instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); - instantiationService.stub(IFileService, new TestFileService()); - instantiationService.stub(IBackupFileService, new TestBackupFileService()); - instantiationService.stub(ITelemetryService, NullTelemetryService); - instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); - instantiationService.stub(IMenuService, new TestMenuService()); - instantiationService.stub(IKeybindingService, new MockKeybindingService()); - instantiationService.stub(IDecorationsService, new TestDecorationsService()); - instantiationService.stub(IExtensionService, new TestExtensionService()); - instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : instantiationService.createInstance(TestTextFileService)); - instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); - instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - instantiationService.stub(IThemeService, new TestThemeService()); - instantiationService.stub(ILogService, new NullLogService()); - const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); - instantiationService.stub(IEditorGroupsService, editorGroupService); - instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); - const editorService = new TestEditorService(editorGroupService); - instantiationService.stub(IEditorService, editorService); - instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); - instantiationService.stub(IViewletService, new TestViewletService()); - instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); +export class TestProgressService implements IProgressService { - return instantiationService; + _serviceBrand: undefined; + + withProgress( + options: IProgressOptions | IProgressWindowOptions | IProgressNotificationOptions | IProgressCompositeOptions, + task: (progress: IProgress) => Promise, + onDidCancel?: ((choice?: number | undefined) => void) | undefined + ): Promise { + return task(Progress.None); + } } export class TestAccessibilityService implements IAccessibilityService { @@ -239,8 +302,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; @@ -264,10 +325,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; } @@ -275,14 +336,7 @@ export class TestHistoryService implements IHistoryService { openLastEditLocation(): void { } } -export class TestDialogService implements IDialogService { - _serviceBrand: undefined; - - confirm(_confirmation: IConfirmation): Promise { return Promise.resolve({ confirmed: false }); } - show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } - about(): Promise { return Promise.resolve(); } -} export class TestFileDialogService implements IFileDialogService { @@ -336,7 +390,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 { } @@ -355,7 +408,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 { } @@ -364,6 +416,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; @@ -403,7 +456,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.'); } @@ -412,6 +465,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; @@ -427,7 +493,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidLayout: Event = Event.None; onDidEditorPartOptionsChange = Event.None; - orientation: any; + orientation = GroupOrientation.HORIZONTAL; whenRestored: Promise = Promise.resolve(undefined); willRestoreEditors = false; @@ -446,7 +512,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { setSize(_group: number | IEditorGroup, _size: { width: number, height: number }): void { } arrangeGroups(_arrangement: GroupsArrangement): void { } applyLayout(_layout: EditorGroupLayout): void { } - setGroupOrientation(_orientation: any): void { } + setGroupOrientation(_orientation: GroupOrientation): void { } addGroup(_location: number | IEditorGroup, _direction: GroupDirection, _options?: IAddGroupOptions): IEditorGroup { throw new Error('not implemented'); } removeGroup(_group: number | IEditorGroup): void { } moveGroup(_group: number | IEditorGroup, _location: number | IEditorGroup, _direction: GroupDirection): IEditorGroup { throw new Error('not implemented'); } @@ -464,7 +530,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; @@ -495,9 +561,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 { } @@ -549,47 +615,52 @@ export class TestEditorService implements EditorServiceImpl { onDidOpenEditorFail: Event = Event.None; onDidMostRecentlyActiveEditorsChange: Event = Event.None; - activeControl!: IVisibleEditor; - activeTextEditorWidget: any; - activeTextEditorMode: any; - activeEditor!: IEditorInput; + 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; constructor(private editorGroupService?: IEditorGroupsService) { } getEditors() { return []; } + getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { return []; } overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); } - openEditor(_editor: any, _options?: any, _group?: any): Promise { throw new Error('not implemented'); } - doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { + 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 | 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 | IResourceInput | IUntitledTextResourceInput): boolean { return false; } - getOpened(_editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): IEditorInput { throw new Error('not implemented'); } + 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 { _serviceBrand: undefined; - private readonly _onFileChanges: Emitter; - private readonly _onAfterOperation: Emitter; + private readonly _onDidFilesChange = new Emitter(); + private readonly _onDidRunOperation = new Emitter(); readonly onWillActivateFileSystemProvider = Event.None; readonly onDidChangeFileSystemProviderCapabilities = Event.None; @@ -598,18 +669,13 @@ export class TestFileService implements IFileService { private content = 'Hello Html'; private lastReadFileUri!: URI; - constructor() { - this._onFileChanges = new Emitter(); - this._onAfterOperation = new Emitter(); - } - setContent(content: string): void { this.content = content; } getContent(): string { return this.content; } getLastReadFileUri(): URI { return this.lastReadFileUri; } - get onFileChanges(): Event { return this._onFileChanges.event; } - fireFileChanges(event: FileChangesEvent): void { this._onFileChanges.fire(event); } - get onAfterOperation(): Event { return this._onAfterOperation.event; } - fireAfterOperation(event: FileOperationEvent): void { this._onAfterOperation.fire(event); } + get onDidFilesChange(): Event { return this._onDidFilesChange.event; } + fireFileChanges(event: FileChangesEvent): void { this._onDidFilesChange.fire(event); } + get onDidRunOperation(): Event { return this._onDidRunOperation.event; } + fireAfterOperation(event: FileOperationEvent): void { this._onDidRunOperation.fire(event); } resolve(resource: URI, _options?: IResolveFileOptions): Promise; resolve(resource: URI, _options: IResolveMetadataFileOptions): Promise; resolve(resource: URI, _options?: IResolveFileOptions): Promise { @@ -724,7 +790,14 @@ export class TestFileService implements IFileService { } del(_resource: URI, _options?: { useTrash?: boolean, recursive?: boolean }): Promise { return Promise.resolve(); } - watch(_resource: URI): IDisposable { return Disposable.None; } + + readonly watches: URI[] = []; + watch(_resource: URI): IDisposable { + this.watches.push(_resource); + + return toDisposable(() => this.watches.splice(this.watches.indexOf(_resource), 1)); + } + getWriteEncoding(_resource: URI): IResourceEncoding { return { encoding: 'utf8', hasBOM: false }; } dispose(): void { } } @@ -741,6 +814,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(); @@ -749,32 +823,6 @@ export class TestBackupFileService implements IBackupFileService { } } -export class TestCodeEditorService implements ICodeEditorService { - _serviceBrand: undefined; - - onCodeEditorAdd: Event = Event.None; - onCodeEditorRemove: Event = Event.None; - onDiffEditorAdd: Event = Event.None; - onDiffEditorRemove: Event = Event.None; - onDidChangeTransientModelProperty: Event = Event.None; - - addCodeEditor(_editor: ICodeEditor): void { } - removeCodeEditor(_editor: ICodeEditor): void { } - listCodeEditors(): ICodeEditor[] { return []; } - addDiffEditor(_editor: IDiffEditor): void { } - removeDiffEditor(_editor: IDiffEditor): void { } - listDiffEditors(): IDiffEditor[] { return []; } - getFocusedCodeEditor(): ICodeEditor | null { return null; } - registerDecorationType(_key: string, _options: IDecorationRenderOptions, _parentTypeKey?: string): void { } - removeDecorationType(_key: string): void { } - resolveDecorationOptions(_typeKey: string, _writable: boolean): IModelDecorationOptions { return Object.create(null); } - setTransientModelProperty(_model: ITextModel, _key: string, _value: any): void { } - getTransientModelProperty(_model: ITextModel, _key: string) { } - getTransientModelProperties(_model: ITextModel) { return undefined; } - getActiveCodeEditor(): ICodeEditor | null { return null; } - openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(null); } -} - export class TestLifecycleService implements ILifecycleService { _serviceBrand: undefined; @@ -864,9 +912,17 @@ export class TestHostService implements IHostService { _serviceBrand: undefined; - readonly hasFocus: boolean = true; - async hadLastFocus(): Promise { return true; } - readonly onDidChangeFocus: Event = Event.None; + private _hasFocus = true; + get hasFocus() { return this._hasFocus; } + async hadLastFocus(): Promise { return this._hasFocus; } + + private _onDidChangeFocus = new Emitter(); + readonly onDidChangeFocus = this._onDidChangeFocus.event; + + setFocus(focus: boolean) { + this._hasFocus = focus; + this._onDidChangeFocus.fire(this._hasFocus); + } async restart(): Promise { } async reload(): Promise { } @@ -884,3 +940,180 @@ export class TestFilesConfigurationService extends FilesConfigurationService { super.onFilesConfigurationChange(configuration); } } + +export class TestReadonlyTextFileEditorModel extends TextFileEditorModel { + + isReadonly(): boolean { + return true; + } +} + +export class TestEditorInput extends EditorInput { + + constructor(public resource: URI, private typeId: string) { + super(); + } + + getTypeId(): string { + return this.typeId; + } + + resolve(): Promise { + return Promise.resolve(null); + } +} + +export function registerTestEditor(id: string, inputs: SyncDescriptor[], factoryInputId?: string): IDisposable { + class TestEditorControl extends BaseEditor { + + constructor() { super(id, NullTelemetryService, new TestThemeService(), new TestStorageService()); } + + async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + super.setInput(input, options, token); + + await input.resolve(); + } + + getId(): string { return id; } + layout(): void { } + createEditor(): void { } + } + + const disposables = new DisposableStore(); + + disposables.add(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, id, 'Test Editor Control'), inputs)); + + if (factoryInputId) { + + interface ISerializedTestInput { + resource: string; + } + + class EditorsObserverTestEditorInputFactory implements IEditorInputFactory { + + canSerialize(editorInput: EditorInput): boolean { + return true; + } + + serialize(editorInput: EditorInput): string { + let testEditorInput = editorInput; + let testInput: ISerializedTestInput = { + resource: testEditorInput.resource.toString() + }; + + return JSON.stringify(testInput); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { + let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); + + return new TestFileEditorInput(URI.parse(testInput.resource), factoryInputId!); + } + } + + disposables.add(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(factoryInputId, EditorsObserverTestEditorInputFactory)); + } + + return disposables; +} + +export class TestFileEditorInput extends EditorInput implements IFileEditorInput { + gotDisposed = false; + gotSaved = false; + gotSavedAs = false; + gotReverted = false; + dirty = false; + private fails = false; + + constructor( + public resource: URI, + private typeId: string + ) { + super(); + } + + getTypeId() { return this.typeId; } + resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } + matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestFileEditorInput && other.getTypeId() === this.typeId; } + setEncoding(encoding: string) { } + getEncoding() { return undefined; } + setPreferredEncoding(encoding: string) { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } + setForceOpenAsBinary(): void { } + setFailToOpen(): void { + this.fails = true; + } + async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSaved = true; + return this; + } + async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSavedAs = true; + return this; + } + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + this.gotReverted = true; + this.gotSaved = false; + this.gotSavedAs = false; + } + setDirty(): void { this.dirty = true; } + isDirty(): boolean { + return this.dirty; + } + isReadonly(): boolean { + return false; + } + isResolved(): boolean { return false; } + dispose(): void { + super.dispose(); + this.gotDisposed = true; + } + movedEditor: IMoveResult | undefined = undefined; + move(): IMoveResult | undefined { return this.movedEditor; } +} + +export class TestEditorPart extends EditorPart { + + saveState(): void { + return super.saveState(); + } + + clearState(): void { + const workspaceMemento = this.getMemento(StorageScope.WORKSPACE); + for (const key of Object.keys(workspaceMemento)) { + delete workspaceMemento[key]; + } + + const globalMemento = this.getMemento(StorageScope.GLOBAL); + for (const key of Object.keys(globalMemento)) { + delete globalMemento[key]; + } + } +} + +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(this.environmentService.userHome!); } + get userHomeSync() { return 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..091bc24e2f3 --- /dev/null +++ b/src/vs/workbench/test/common/api/semanticTokensDto.test.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import { VSBuffer } from 'vs/base/common/buffer'; + +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) + }] + }); + }); + + test('issue #94521: unusual backing array buffer', () => { + function wrapAndSliceUint8Arry(buff: Uint8Array, prefixLength: number, suffixLength: number): Uint8Array { + const wrapped = new Uint8Array(prefixLength + buff.byteLength + suffixLength); + wrapped.set(buff, prefixLength); + return wrapped.subarray(prefixLength, prefixLength + buff.byteLength); + } + function wrapAndSlice(buff: VSBuffer, prefixLength: number, suffixLength: number): VSBuffer { + return VSBuffer.wrap(wrapAndSliceUint8Arry(buff.buffer, prefixLength, suffixLength)); + } + const dto: ISemanticTokensDto = { + id: 5, + type: 'full', + data: new Uint32Array([1, 2, 3, 4, 5]) + }; + const encoded = encodeSemanticTokensDto(dto); + + // with misaligned prefix and misaligned suffix + assertEqualFull(decodeSemanticTokensDto(wrapAndSlice(encoded, 1, 1)), dto); + // with misaligned prefix and aligned suffix + assertEqualFull(decodeSemanticTokensDto(wrapAndSlice(encoded, 1, 4)), dto); + // with aligned prefix and misaligned suffix + assertEqualFull(decodeSemanticTokensDto(wrapAndSlice(encoded, 4, 1)), dto); + // with aligned prefix and aligned suffix + assertEqualFull(decodeSemanticTokensDto(wrapAndSlice(encoded, 4, 4)), dto); + }); +}); 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 b8a5e1b1ee1..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'; @@ -23,6 +23,7 @@ suite('Notifications', () => { let item3 = NotificationViewItem.create({ severity: Severity.Info, message: 'Info Message' })!; let item4 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', source: 'Source' })!; let item5 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] } })!; + let item6 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] }, progress: { infinite: true } })!; assert.equal(item1.equals(item1), true); assert.equal(item2.equals(item2), true); @@ -35,13 +36,17 @@ suite('Notifications', () => { assert.equal(item1.equals(item4), false); assert.equal(item1.equals(item5), false); + // Progress + assert.equal(item1.hasProgress, false); + assert.equal(item6.hasProgress, true); + // Message Box assert.equal(item5.canCollapse, false); assert.equal(item5.expanded, true); // Events let called = 0; - item1.onDidExpansionChange(() => { + item1.onDidChangeExpansion(() => { called++; }); @@ -53,8 +58,8 @@ suite('Notifications', () => { assert.equal(called, 2); called = 0; - item1.onDidLabelChange(e => { - if (e.kind === NotificationViewItemLabelKind.PROGRESS) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.PROGRESS) { called++; } }); @@ -65,8 +70,8 @@ suite('Notifications', () => { assert.equal(called, 2); called = 0; - item1.onDidLabelChange(e => { - if (e.kind === NotificationViewItemLabelKind.MESSAGE) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.MESSAGE) { called++; } }); @@ -74,8 +79,8 @@ suite('Notifications', () => { item1.updateMessage('message update'); called = 0; - item1.onDidLabelChange(e => { - if (e.kind === NotificationViewItemLabelKind.SEVERITY) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.SEVERITY) { called++; } }); @@ -83,8 +88,8 @@ suite('Notifications', () => { item1.updateSeverity(Severity.Error); called = 0; - item1.onDidLabelChange(e => { - if (e.kind === NotificationViewItemLabelKind.ACTIONS) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.ACTIONS) { called++; } }); @@ -93,6 +98,17 @@ suite('Notifications', () => { assert.equal(called, 1); + called = 0; + item1.onDidChangeVisibility(e => { + called++; + }); + + item1.updateVisibility(true); + item1.updateVisibility(false); + item1.updateVisibility(false); + + assert.equal(called, 2); + called = 0; item1.onDidClose(() => { called++; @@ -102,31 +118,8 @@ suite('Notifications', () => { assert.equal(called, 1); // Error with Action - let item6 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!; - assert.equal(item6.actions!.primary!.length, 1); - - // Links - let item7 = NotificationViewItem.create({ severity: Severity.Info, message: 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and [Link 3](command:without.title) and [Invalid Link4](ftp://link4.com)' })!; - - const links = item7.message.links; - assert.equal(links.length, 3); - assert.equal(links[0].name, 'Link 1'); - assert.equal(links[0].href, 'http://link1.com'); - assert.equal(links[0].title, 'http://link1.com'); - assert.equal(links[0].length, '[Link 1](http://link1.com)'.length); - assert.equal(links[0].offset, 'Unable to '.length); - - assert.equal(links[1].name, 'Link 2'); - assert.equal(links[1].href, 'command:open.me'); - assert.equal(links[1].title, 'Open This'); - assert.equal(links[1].length, '[Link 2](command:open.me "Open This")'.length); - assert.equal(links[1].offset, 'Unable to [Link 1](http://link1.com) open '.length); - - assert.equal(links[2].name, 'Link 3'); - assert.equal(links[2].href, 'command:without.title'); - assert.equal(links[2].title, 'Click to execute command \'without.title\''); - assert.equal(links[2].length, '[Link 3](command:without.title)'.length); - assert.equal(links[2].offset, 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and '.length); + let item7 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!; + assert.equal(item7.actions!.primary!.length, 1); // Filter let item8 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.SILENT)!; @@ -146,12 +139,12 @@ suite('Notifications', () => { const model = new NotificationsModel(); let lastNotificationEvent!: INotificationChangeEvent; - model.onDidNotificationChange(e => { + model.onDidChangeNotification(e => { lastNotificationEvent = e; }); let lastStatusMessageEvent!: IStatusMessageChangeEvent; - model.onDidStatusMessageChange(e => { + model.onDidChangeStatusMessage(e => { lastStatusMessageEvent = e; }); @@ -162,19 +155,35 @@ suite('Notifications', () => { let item1Handle = model.addNotification(item1); assert.equal(lastNotificationEvent.item.severity, item1.severity); - assert.equal(lastNotificationEvent.item.message.value, item1.message); + assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item1.message); 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.value, item2.message); + assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2.message); assert.equal(lastNotificationEvent.index, 0); assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); model.addNotification(item3); assert.equal(lastNotificationEvent.item.severity, item3.severity); - assert.equal(lastNotificationEvent.item.message.value, item3.message); + assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item3.message); assert.equal(lastNotificationEvent.index, 0); assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); @@ -189,29 +198,29 @@ suite('Notifications', () => { assert.equal(called, 1); assert.equal(model.notifications.length, 2); assert.equal(lastNotificationEvent.item.severity, item1.severity); - assert.equal(lastNotificationEvent.item.message.value, item1.message); + assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item1.message); assert.equal(lastNotificationEvent.index, 2); assert.equal(lastNotificationEvent.kind, NotificationChangeType.REMOVE); model.addNotification(item2Duplicate); assert.equal(model.notifications.length, 2); assert.equal(lastNotificationEvent.item.severity, item2Duplicate.severity); - assert.equal(lastNotificationEvent.item.message.value, item2Duplicate.message); + assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2Duplicate.message); assert.equal(lastNotificationEvent.index, 0); assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); item2Handle.close(); assert.equal(model.notifications.length, 1); assert.equal(lastNotificationEvent.item.severity, item2Duplicate.severity); - assert.equal(lastNotificationEvent.item.message.value, item2Duplicate.message); + assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2Duplicate.message); assert.equal(lastNotificationEvent.index, 0); assert.equal(lastNotificationEvent.kind, NotificationChangeType.REMOVE); model.notifications[0].expand(); assert.equal(lastNotificationEvent.item.severity, item3.severity); - assert.equal(lastNotificationEvent.item.message.value, item3.message); + 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 dfe948f1797..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 { @@ -35,16 +36,22 @@ export class TestTextResourcePropertiesService implements ITextResourcePropertie } export class TestContextService implements IWorkspaceContextService { + _serviceBrand: undefined; private workspace: Workspace; - private options: any; + private options: object; private readonly _onDidChangeWorkspaceName: Emitter; - private readonly _onDidChangeWorkspaceFolders: Emitter; - private readonly _onDidChangeWorkbenchState: Emitter; + get onDidChangeWorkspaceName(): Event { return this._onDidChangeWorkspaceName.event; } - constructor(workspace: any = TestWorkspace, options: any = null) { + private readonly _onDidChangeWorkspaceFolders: Emitter; + get onDidChangeWorkspaceFolders(): Event { return this._onDidChangeWorkspaceFolders.event; } + + private readonly _onDidChangeWorkbenchState: Emitter; + get onDidChangeWorkbenchState(): Event { return this._onDidChangeWorkbenchState.event; } + + constructor(workspace = TestWorkspace, options = null) { this.workspace = workspace; this.options = options || Object.create(null); this._onDidChangeWorkspaceName = new Emitter(); @@ -52,18 +59,6 @@ export class TestContextService implements IWorkspaceContextService { this._onDidChangeWorkbenchState = new Emitter(); } - get onDidChangeWorkspaceName(): Event { - return this._onDidChangeWorkspaceName.event; - } - - get onDidChangeWorkspaceFolders(): Event { - return this._onDidChangeWorkspaceFolders.event; - } - - get onDidChangeWorkbenchState(): Event { - return this._onDidChangeWorkbenchState.event; - } - getFolders(): IWorkspaceFolder[] { return this.workspace ? this.workspace.folders : []; } @@ -100,9 +95,7 @@ export class TestContextService implements IWorkspaceContextService { return this.options; } - updateOptions() { - - } + updateOptions() { } isInsideWorkspace(resource: URI): boolean { if (resource && this.workspace) { @@ -135,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 10560e35590..00000000000 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ /dev/null @@ -1,190 +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'; - -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 instantiationService = new InstantiationService(new ServiceCollection( - [ITelemetryService, telemetryService], - [IConfigurationService, configurationService], - [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), new NullLogService())], - [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 ac995be33b1..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'; @@ -36,6 +36,13 @@ import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; // declare var __dirname: string; @@ -62,11 +69,17 @@ suite.skip('TextSearch performance (integration)', () => { const configurationService = new TestConfigurationService(); const textResourcePropertiesService = new TestTextResourcePropertiesService(configurationService); const logService = new NullLogService(); + 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], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService)], + [IDialogService, dialogService], + [INotificationService, notificationService], + [IUndoRedoService, 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 0857cc45c11..13920be7069 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService } 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 { NativeTextFileService } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; +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'; import { FileOperationError, IFileService } from 'vs/platform/files/common/files'; @@ -22,20 +22,31 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; 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 { 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'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +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, IOpenedWindow } 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, @@ -43,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; @@ -54,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, @@ -62,8 +73,9 @@ export class TestTextFileService extends NativeTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @INotificationService notificationService: INotificationService, - @IRemotePathService remotePathService: IRemotePathService + @IRemotePathService remotePathService: IRemotePathService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, + @ILogService logService: ILogService ) { super( fileService, @@ -79,8 +91,9 @@ export class TestTextFileService extends NativeTextFileService { filesConfigurationService, textModelService, codeEditorService, - notificationService, - remotePathService + remotePathService, + workingCopyFileService, + logService ); } @@ -110,6 +123,31 @@ export class TestTextFileService extends NativeTextFileService { } } +export class TestNativeTextFileServiceWithEncodingOverrides extends NativeTextFileService { + + private _testEncoding: TestEncodingOracle | undefined; + get encoding(): TestEncodingOracle { + if (!this._testEncoding) { + this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); + } + + return this._testEncoding; + } +} + +class TestEncodingOracle extends EncodingOracle { + + protected get encodingOverrides(): IEncodingOverride[] { + return [ + { extension: 'utf16le', encoding: UTF16le }, + { extension: 'utf16be', encoding: UTF16be }, + { extension: 'utf8bom', encoding: UTF8_with_bom } + ]; + } + + protected set encodingOverrides(overrides: IEncodingOverride[]) { } +} + export class TestSharedProcessService implements ISharedProcessService { _serviceBrand: undefined; @@ -171,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 { } @@ -187,3 +226,20 @@ export function workbenchInstantiationService(): ITestInstantiationService { return instantiationService; } + +export class TestServiceAccessor { + constructor( + @ILifecycleService public lifecycleService: TestLifecycleService, + @ITextFileService public textFileService: TestTextFileService, + @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, + @IWorkspaceContextService public contextService: TestContextService, + @IModelService public modelService: ModelServiceImpl, + @IFileService public fileService: TestFileService, + @IElectronService public electronService: TestElectronService, + @IFileDialogService public fileDialogService: TestFileDialogService, + @IBackupFileService public backupFileService: NodeTestBackupFileService, + @IWorkingCopyService public workingCopyService: IWorkingCopyService, + @IEditorService public editorService: IEditorService + ) { + } +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 9debc489ee1..7288803da97 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'; @@ -57,6 +52,7 @@ import 'vs/workbench/browser/parts/views/views'; //#region --- workbench services +import 'vs/platform/undoRedo/common/undoRedoService'; import 'vs/workbench/services/extensions/browser/extensionUrlHandler'; import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; @@ -83,8 +79,10 @@ import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/path/common/remotePathService'; import 'vs/workbench/services/remote/common/remoteExplorerService'; 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'; @@ -142,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'; @@ -162,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'; @@ -173,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'; @@ -191,11 +190,10 @@ import 'vs/workbench/contrib/url/common/url.contribution'; // Webview import 'vs/workbench/contrib/webview/browser/webview.contribution'; -import 'vs/workbench/contrib/customEditor/browser/webviewEditor.contribution'; +import 'vs/workbench/contrib/customEditor/browser/customEditor.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 @@ -203,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 @@ -272,6 +270,9 @@ import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; // Code Actions import 'vs/workbench/contrib/codeActions/common/codeActions.contribution'; +// Welcome +import 'vs/workbench/contrib/welcome/common/viewsWelcome.contribution'; + // Timeline import 'vs/workbench/contrib/timeline/browser/timeline.contribution'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index c0bee3d2971..cdd9d93c1ea 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -39,20 +39,21 @@ import 'vs/workbench/services/keybinding/electron-browser/keybinding.contributio import 'vs/workbench/services/extensions/electron-browser/extensionService'; import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; +import 'vs/workbench/services/extensionManagement/electron-browser/extensionTipsService'; 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/userDataSync/electron-browser/userDataAuthTokenService'; +import 'vs/workbench/services/authentication/electron-browser/authenticationTokenService'; import 'vs/workbench/services/authentication/browser/authenticationService'; import 'vs/workbench/services/host/electron-browser/desktopHostService'; import 'vs/workbench/services/request/electron-browser/requestService'; @@ -69,8 +70,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 +108,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'; @@ -141,6 +148,6 @@ import 'vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribu import 'vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut.contribution'; // Configuration Exporter -import 'vs/workbench/contrib/configExporter/node/configurationExportHelper.contribution'; +import 'vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.contribution'; //#endregion diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 140711b300e..7908789aba5 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,18 +201,27 @@ 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); // Register commands if any if (Array.isArray(options.commands)) { for (const command of options.commands) { - CommandsRegistry.registerCommand(command.id, (accessor, ...args: any[]) => { + 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); }); } } @@ -253,7 +233,6 @@ export { create, IWorkbenchConstructionOptions, - // Basic Types URI, UriComponents, @@ -302,9 +281,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 f1c45265344..82e9da5652f 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -34,6 +34,7 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService'; import 'vs/workbench/services/keybinding/browser/keymapService'; import 'vs/workbench/services/extensions/browser/extensionService'; import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; +import 'vs/workbench/services/extensionManagement/common/extensionTipsService'; import 'vs/workbench/services/telemetry/browser/telemetryService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import 'vs/workbench/services/credentials/browser/credentialsService'; @@ -62,15 +63,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, IUserDataAuthTokenService, 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 { UserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataAuthTokenService'; +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 +85,12 @@ registerSingleton(ILoggerService, FileLoggerService); registerSingleton(IAuthenticationService, AuthenticationService); registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); -registerSingleton(IUserDataAuthTokenService, UserDataAuthTokenService); +registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); +registerSingleton(IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService); +registerSingleton(IAuthenticationTokenService, AuthenticationTokenService); registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService); -registerSingleton(ISettingsSyncService, SettingsSynchroniser); registerSingleton(IUserDataSyncService, UserDataSyncService); +registerSingleton(ITitleService, TitlebarPart); //#endregion @@ -119,7 +125,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/package.json b/test/automation/package.json index a3f3baf2f40..8fced3ba824 100644 --- a/test/automation/package.json +++ b/test/automation/package.json @@ -28,7 +28,8 @@ "concurrently": "^3.5.1", "cpx": "^1.5.0", "typescript": "3.7.5", - "watch": "^1.0.2" + "watch": "^1.0.2", + "tree-kill": "1.2.2" }, "dependencies": { "mkdirp": "^0.5.1", 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 3b5a667f81f..e9ac876245d 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -115,9 +115,6 @@ async function createDriverHandle(): Promise { } export async function spawn(options: SpawnOptions): Promise { - const codePath = options.codePath; - const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath(); - const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); const handle = await createDriverHandle(); let child: cp.ChildProcess | undefined; @@ -126,63 +123,65 @@ export async function spawn(options: SpawnOptions): Promise { if (options.web) { await launch(options.userDataDir, options.workspacePath, options.codePath); connectDriver = connectPlaywrightDriver.bind(connectPlaywrightDriver, options.browser); - } else { - const env = process.env; - - const args = [ - options.workspacePath, - '--skip-getting-started', - '--skip-release-notes', - '--sticky-quickopen', - '--disable-telemetry', - '--disable-updates', - '--disable-crash-reporter', - `--extensions-dir=${options.extensionsPath}`, - `--user-data-dir=${options.userDataDir}`, - '--driver', handle - ]; - - if (options.remote) { - // Replace workspace path with URI - args[0] = `--${options.workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(options.workspacePath).path}`; - - if (codePath) { - // running against a build: copy the test resolver extension - const testResolverExtPath = path.join(options.extensionsPath, 'vscode-test-resolver'); - if (!fs.existsSync(testResolverExtPath)) { - const orig = path.join(repoPath, 'extensions', 'vscode-test-resolver'); - await new Promise((c, e) => ncp(orig, testResolverExtPath, err => err ? e(err) : c())); - } - } - args.push('--enable-proposed-api=vscode.vscode-test-resolver'); - const remoteDataDir = `${options.userDataDir}-server`; - mkdirp.sync(remoteDataDir); - env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; - } - - if (!codePath) { - args.unshift(repoPath); - } - - if (options.verbose) { - args.push('--driver-verbose'); - } - - if (options.log) { - args.push('--log', options.log); - } - - if (options.extraArgs) { - args.push(...options.extraArgs); - } - - const spawnOptions: cp.SpawnOptions = { env }; - child = cp.spawn(electronPath, args, spawnOptions); - instances.add(child); - child.once('exit', () => instances.delete(child!)); - connectDriver = connectElectronDriver; + return connect(connectDriver, child, '', handle, options.logger); } + const env = process.env; + const codePath = options.codePath; + const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); + + const args = [ + options.workspacePath, + '--skip-getting-started', + '--disable-telemetry', + '--disable-updates', + '--disable-crash-reporter', + `--extensions-dir=${options.extensionsPath}`, + `--user-data-dir=${options.userDataDir}`, + `--disable-restore-windows`, + '--driver', handle + ]; + + if (options.remote) { + // Replace workspace path with URI + args[0] = `--${options.workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(options.workspacePath).path}`; + + if (codePath) { + // running against a build: copy the test resolver extension + const testResolverExtPath = path.join(options.extensionsPath, 'vscode-test-resolver'); + if (!fs.existsSync(testResolverExtPath)) { + const orig = path.join(repoPath, 'extensions', 'vscode-test-resolver'); + await new Promise((c, e) => ncp(orig, testResolverExtPath, err => err ? e(err) : c())); + } + } + args.push('--enable-proposed-api=vscode.vscode-test-resolver'); + const remoteDataDir = `${options.userDataDir}-server`; + mkdirp.sync(remoteDataDir); + env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; + } + + if (!codePath) { + args.unshift(repoPath); + } + + if (options.verbose) { + args.push('--driver-verbose'); + } + + if (options.log) { + args.push('--log', options.log); + } + + if (options.extraArgs) { + args.push(...options.extraArgs); + } + + const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath(); + const spawnOptions: cp.SpawnOptions = { env }; + child = cp.spawn(electronPath, args, spawnOptions); + instances.add(child); + child.once('exit', () => instances.delete(child!)); + connectDriver = connectElectronDriver; return connect(connectDriver, child, outPath, handle, options.logger); } 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/keybindings.ts b/test/automation/src/keybindings.ts index 0e8739dd04c..abd0ccf9055 100644 --- a/test/automation/src/keybindings.ts +++ b/test/automation/src/keybindings.ts @@ -11,7 +11,7 @@ export class KeybindingsEditor { constructor(private code: Code) { } - async updateKeybinding(command: string, keybinding: string, ariaLabel: string): Promise { + async updateKeybinding(command: string, keybinding: string, title: string): Promise { if (process.platform === 'darwin') { await this.code.dispatchKeybinding('cmd+k cmd+s'); } else { @@ -29,6 +29,6 @@ export class KeybindingsEditor { await this.code.dispatchKeybinding(keybinding); await this.code.dispatchKeybinding('enter'); - await this.code.waitForElement(`.keybindings-list-container div[aria-label="Keybinding is ${ariaLabel}."]`); + await this.code.waitForElement(`.keybindings-list-container .keybinding-label div[title="${title}"]`); } } diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index 40ee4c82851..cc0c5547db3 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -10,6 +10,7 @@ import { mkdir } from 'fs'; import { promisify } from 'util'; import { IDriver, IDisposable } from './driver'; import { URI } from 'vscode-uri'; +import * as kill from 'tree-kill'; const width = 1200; const height = 800; @@ -93,6 +94,7 @@ let workspacePath: string | undefined; export async function launch(userDataDir: string, _workspacePath: string, codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH): Promise { workspacePath = _workspacePath; + const agentFolder = userDataDir; await promisify(mkdir)(agentFolder); const env = { @@ -121,7 +123,7 @@ export async function launch(userDataDir: string, _workspacePath: string, codeSe function teardown(): void { if (server) { - server.kill(); + kill(server.pid); server = undefined; } } @@ -137,13 +139,9 @@ function waitForEndpoint(): Promise { }); } -export function connect(engine: 'chromium' | 'webkit' | 'firefox' = 'chromium'): Promise<{ client: IDisposable, driver: IDriver }> { +export function connect(browserType: 'chromium' | 'webkit' | 'firefox' = 'chromium'): Promise<{ client: IDisposable, driver: IDriver }> { return new Promise(async (c) => { - const browser = await playwright[engine].launch({ - // Run in Edge dev on macOS - // executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev', - headless: false - }); + const browser = await playwright[browserType].launch({ headless: false, dumpio: true }); const context = await browser.newContext(); const page = await context.newPage(); await page.setViewportSize({ width, height }); 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/automation/yarn.lock b/test/automation/yarn.lock index 98c63c91e0d..11785373d40 100644 --- a/test/automation/yarn.lock +++ b/test/automation/yarn.lock @@ -1634,6 +1634,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +tree-kill@1.2.2: + 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== + tree-kill@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a" 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 8b41d05ed08..8f1d567c3e6 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -7,19 +7,18 @@ 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 # Dev (Web) -yarn smoketest --web --browser [chromium|firefox|webkit] +yarn smoketest --web --browser [chromium|webkit] # Build (Electron) yarn smoketest --build --stable-build # Build (Web - read instructions below) -yarn smoketest --build --web --browser [chromium|firefox|webkit] +yarn smoketest --build --web --browser [chromium|webkit] # Remote (Electron) yarn smoketest --build --remote @@ -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/css/css.test.ts b/test/smoke/src/areas/languages/languages.test.ts similarity index 73% rename from test/smoke/src/areas/css/css.test.ts rename to test/smoke/src/areas/languages/languages.test.ts index aa6ccb9590b..6d7a7d1f039 100644 --- a/test/smoke/src/areas/css/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('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 5db5fe2b74a..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 => /debug/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 => /erweiterungen/i.test(title)); + // 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 60af92f8694..caa9913a4da 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 setupDataCSSTests } from './areas/css/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'; @@ -56,8 +56,7 @@ const opts = minimist(args, { boolean: [ 'verbose', 'remote', - 'web', - 'ci' + 'web' ], default: { verbose: false @@ -297,24 +296,15 @@ describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { app.logger.log('*** Test start:', title); }); } - - // CI only tests (must be reliable) - if (opts.ci) { - // TODO@Ben figure out tests that can run continously and reliably - } - - // Non-CI execution (all tests) - else { - if (!opts.web) { setupDataMigrationTests(opts['stable-build'], testDataPath); } - if (!opts.web) { setupDataLossTests(); } - if (!opts.web) { setupDataPreferencesTests(); } - setupDataSearchTests(); - setupDataCSSTests(); - setupDataEditorTests(); - setupDataStatusbarTests(!!opts.web); - if (!opts.web) { setupDataExtensionTests(); } - if (!opts.web) { setupDataMultirootTests(); } - if (!opts.web) { setupDataLocalizationTests(); } - if (!opts.web) { setupLaunchTests(); } - } + if (!opts.web) { setupDataMigrationTests(opts['stable-build'], testDataPath); } + if (!opts.web) { setupDataLossTests(); } + if (!opts.web) { setupDataPreferencesTests(); } + setupDataSearchTests(); + setupDataLanguagesTests(); + setupDataEditorTests(); + setupDataStatusbarTests(!!opts.web); + if (!opts.web) { setupDataExtensionTests(); } + if (!opts.web) { setupDataMultirootTests(); } + if (!opts.web) { setupDataLocalizationTests(); } + if (!opts.web) { setupLaunchTests(); } }); 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 4172285834f..cf338465a74 100644 --- a/test/unit/README.md +++ b/test/unit/README.md @@ -4,11 +4,13 @@ ./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 +For instance, `./scripts/test.sh --debug --glob **/extHost*.test.js` runs all tests from `extHost`-files and enables you to debug them. + ## Run (inside browser) yarn test-browser --browser webkit --browser chromium @@ -24,11 +26,6 @@ Unit tests from layers `common` and `browser` are run inside `chromium`, `webkit yarn run mocha --run src/vs/editor/test/browser/controller/cursor.test.ts -## Debug - -To debug tests use `--debug` when running the test script. Also, the set of tests can be reduced with the `--run` and `--runGlob` flags. Both require a file path/pattern. Like so: - - ./scripts/test.sh --debug --runGrep **/extHost*.test.js ## Coverage diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index d6e15c5e28d..923bc7d52cf 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -22,7 +22,7 @@ const optimist = require('optimist') .describe('run', 'only run tests matching ').string('run') .describe('glob', 'only run tests matching ').string('glob') .describe('debug', 'do not run browsers headless').boolean('debug') - .describe('browser', 'browsers in which tests should run').string('browser').default('browser', ['chromium']) + .describe('browser', 'browsers in which tests should run').string('browser').default('browser', ['chromium', 'firefox', 'webkit']) .describe('reporter', 'the mocha reporter').string('reporter').default('reporter', defaultReporterName) .describe('reporter-options', 'the mocha reporter options').string('reporter-options').default('reporter-options', '') .describe('tfs', 'tfs').string('tfs') @@ -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 e4f5accd3c7..02f896be342 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" @@ -614,6 +619,11 @@ ajv-keywords@^3.1.0: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= +ajv-keywords@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" + integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== + ajv@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda" @@ -1218,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" @@ -1396,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" @@ -1419,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" @@ -1443,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" @@ -1515,7 +1516,7 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= -camelcase@^5.0.0: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -1671,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" @@ -2199,6 +2205,24 @@ css-color-names@0.0.4: resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= +css-loader@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.2.0.tgz#bb570d89c194f763627fcf1f80059c6832d009b2" + integrity sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ== + dependencies: + camelcase "^5.3.1" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^1.2.3" + normalize-path "^3.0.0" + postcss "^7.0.17" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.2" + postcss-modules-scope "^2.1.0" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.0.0" + schema-utils "^2.0.0" + css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -2214,6 +2238,11 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" integrity sha1-lGfQMsOM+u+58teVASUwYvh/ob0= +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + cssnano@^3.0.0: version "3.10.0" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" @@ -2358,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" @@ -2671,10 +2707,10 @@ electron-to-chromium@^1.2.7: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" integrity sha1-eOy4o5kGYYe7N07t412ccFZagD0= -electron@7.1.11: - version "7.1.11" - resolved "https://registry.yarnpkg.com/electron/-/electron-7.1.11.tgz#3a95e35804af083d58b590513afea5e2290c4b66" - integrity sha512-YDXfnovKY+8iZ5ISQh1kRqYIRKbpOSxGXCx2WVxPFPutEQ7Q/Xzr3h4GePEY25/NXMytMfhKaAZAYjtWUm3r9Q== +electron@7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-7.2.1.tgz#e32a034b299ecee5343b3a3b98487f4f0242d86d" + integrity sha512-Y8ehT8NMR7oltzJ/j5+PD9YU8qbJsYAqiPslwPrZV+e+vl76tiIkRZy78UaJq9gin3ddzpS2Yt0rACsfgYr85w== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" @@ -2727,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" @@ -3272,6 +3315,14 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" +file-loader@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.2.0.tgz#5fb124d2369d7075d70a9a5abecd12e60a95215e" + integrity sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ== + dependencies: + loader-utils "^1.2.3" + schema-utils "^2.0.0" + 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" @@ -4523,6 +4574,13 @@ iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + ieee754@^1.1.11, ieee754@^1.1.4: version "1.1.12" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" @@ -4599,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" @@ -5333,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" @@ -5482,7 +5544,7 @@ loader-runner@^2.3.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" integrity sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI= -loader-utils@1.2.3: +loader-utils@1.2.3, loader-utils@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== @@ -5898,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" @@ -5933,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" @@ -5977,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" @@ -6210,10 +6287,10 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" -node-pty@^0.10.0-beta2: - version "0.10.0-beta2" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta2.tgz#6fd0d2fbbe881869e4e19795a05c557ac958da81" - integrity sha512-IU2lzlPUZ+gKG7pHJjzBHpnuwPTxWGgT3iyQicZfdL7dwLvP5cm00QxavAXCInBmRkOMhvM4aBSKvfzqQnCDBA== +node-pty@0.10.0-beta7: + version "0.10.0-beta7" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta7.tgz#7e383b2d1fe2f34509b57187f5a9a6ff90c46111" + integrity sha512-oC2VyIz9YaIvv6lWjAPZbUzmhLW1ouFmxOogNRNQrKeUzUi2yM/QRmybs+dW/Mhd3V89Yh61Ml0J5yuWiMIBbw== dependencies: nan "^2.14.0" @@ -6567,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= @@ -6922,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" @@ -6933,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" @@ -7124,6 +7200,39 @@ postcss-minify-selectors@^2.0.4: postcss "^5.0.14" postcss-selector-parser "^2.0.0" +postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + dependencies: + postcss "^7.0.5" + +postcss-modules-local-by-default@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" + integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== + dependencies: + icss-utils "^4.1.1" + postcss "^7.0.16" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.0" + +postcss-modules-scope@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz#ad3f5bf7856114f6fcab901b0502e2a2bc39d4eb" + integrity sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + postcss-normalize-charset@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" @@ -7182,6 +7291,15 @@ postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: indexes-of "^1.0.1" uniq "^1.0.1" +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + postcss-svgo@^2.1.1: version "2.1.6" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" @@ -7206,6 +7324,11 @@ postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" integrity sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU= +postcss-value-parser@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" + integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== + postcss-zindex@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" @@ -7225,10 +7348,19 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 source-map "^0.5.6" supports-color "^3.2.3" -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== +postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" + integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +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" @@ -7239,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" @@ -7315,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" @@ -7346,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" @@ -7528,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== @@ -7583,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" @@ -8063,6 +8195,14 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" +schema-utils@^2.0.0, schema-utils@^2.0.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.5.0.tgz#8f254f618d402cc80257486213c8970edfd7c22f" + integrity sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ== + dependencies: + ajv "^6.10.2" + ajv-keywords "^3.4.1" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -8197,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" @@ -8660,6 +8800,14 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== +style-loader@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.0.0.tgz#1d5296f9165e8e2c85d24eee0b7caf9ec8ca1f82" + integrity sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw== + dependencies: + loader-utils "^1.2.3" + schema-utils "^2.0.1" + sudo-prompt@9.1.1: version "9.1.1" resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" @@ -8677,7 +8825,7 @@ supports-color@1.2.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" integrity sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4= -supports-color@6.1.0: +supports-color@6.1.0, supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== @@ -8763,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" @@ -8924,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" @@ -9110,10 +9251,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^3.8.1-rc: - version "3.8.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.1-rc.tgz#f94333c14da70927ccd887be2e91be652a9a09f6" - integrity sha512-aOIe066DyZn2uYIiND6fXMUUJ70nxwu/lKhA92QuQzXyC86fr0ywo1qvO8l2m0EnDcfjprYPuFRgNgDj7U2GlQ== +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" @@ -9315,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" @@ -9523,15 +9659,10 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -vscode-debugprotocol@1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.37.0.tgz#e8c4694a078d18ea1a639553a7a241b35c1e6f6d" - integrity sha512-ppZLEBbFRVNsK0YpfgFi/x2CDyihx0F+UpdKmgeJcvi05UgSXYdO0n9sDVYwoGvvYQPvlpDQeWuY0nloOC4mPA== - -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" @@ -9889,30 +10020,30 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.5.0.tgz#cd3a2f8056084c28e236d4e732da37682010bcc2" - integrity sha512-zLVqVTrg5w2nk9fRj3UuVKCPo/dmFe/cLf3EM9Is5Dm6cgOoXmeo9eq2KgD8A0gquAflTFTf0ya2NaFmShHwyg== +xterm-addon-search@0.6.0-beta.2: + version "0.6.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.6.0-beta.2.tgz#a9408e6c95ad4c47cebc147bfb359ce33f9ccb9f" + integrity sha512-kl7irLdfOdjgCRhlaruGQy2L35BhcOw3dlcogj8HNmHcm98/qF6fO19sOmv7UjZz1ic6sNxtQQw9Sm+MMkxt+A== -xterm-addon-unicode11@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.1.1.tgz#b209ef137db38096f68636af4ef4d0c0acba85ad" - integrity sha512-z6vJTL+dpNljwAYzYoyDjJP8A2XjZuEosl0sRa+FGRf3jEyEVWquDM53MfUd1ztVdAPQ839qR6eYK1BXV04Bhw== +xterm-addon-unicode11@0.2.0-beta.2: + version "0.2.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.2.tgz#2a13ba5b08fdb1005be241816c4e3302674db4af" + integrity sha512-Y047mnIWrAj65TpStdyPYoPeDTX4en+XX4Y90KuQB3cW2xIyZj25NSVV9BZdqzSb7gk9M6KBvIcm8chj7S2N8Q== -xterm-addon-web-links@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" - integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== +xterm-addon-web-links@0.3.0-beta.5: + version "0.3.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.3.0-beta.5.tgz#a489ee89f1e48569760742b20ac349cb61b421cd" + integrity sha512-M+NvTY03TY/yt95xjZFEBgwBThfsYy/RsuJTT4ydDaGeQAJEuZjV2O8nc8gmzAKGxYsgxx9br0A9RyLp5yqKKw== -xterm-addon-webgl@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0.tgz#c1031dc7599cce3509824643ab5f15361c928e3e" - integrity sha512-hQrvabKCnwXFaEZ+YtoJM9Pm0CIBXL5KSwoU+RiGStU3KYTAcqYP2GsH3dWdvKX6kTWhWLS81dtDsGkfbOciuA== +xterm-addon-webgl@0.6.0-beta.3: + version "0.6.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.6.0-beta.3.tgz#48abe4607659caaec3175c0105298bba5e34be27" + integrity sha512-2mhW/4Qv4i4KhEbtOAL4bc9FPGXON8XuM3vfKXT0EauXy/7ygtPu8IqrYNvNo0uJUoW6gOf0d5+/6kUMak2YYg== -xterm@4.5.0-beta.4: - version "4.5.0-beta.4" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.5.0-beta.4.tgz#701f05553b643236d3fcd8bb7f14045bd4537c92" - integrity sha512-Yv1Bf60LTLBMaig1rv033hPz8hQGXZN6VYW2oe/409t2NbJXPg5xZgf47qyaWFV7a5k1BFiwjayJCWaL2nYBew== +xterm@4.5.0-beta.21: + version "4.5.0-beta.21" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.5.0-beta.21.tgz#2265b8bb47c4e9ac68816f423d62f3f52df2bb88" + integrity sha512-np74QU68AwZckkWl5LncLk/HcWT/DUWO1XKJaCKqY/UWc9VlYarTJWSUqZrZiZ6zHJ7LgG9lSzCPSxYvq7Mq5Q== y18n@^3.2.1: version "3.2.1"