Merge branch 'master' into joh/cell-output

This commit is contained in:
Johannes Rieken 2020-09-28 09:08:53 +02:00
commit 6a9500f040
78 changed files with 1810 additions and 1498 deletions

View file

@ -7,7 +7,7 @@ This project incorporates components from the projects listed below. The origina
1. atom/language-clojure version 0.22.7 (https://github.com/atom/language-clojure)
2. atom/language-coffee-script version 0.49.3 (https://github.com/atom/language-coffee-script)
3. atom/language-java version 0.31.4 (https://github.com/atom/language-java)
3. atom/language-java version 0.31.5 (https://github.com/atom/language-java)
4. atom/language-sass version 0.62.1 (https://github.com/atom/language-sass)
5. atom/language-shellscript version 0.26.0 (https://github.com/atom/language-shellscript)
6. atom/language-xml version 0.35.2 (https://github.com/atom/language-xml)
@ -2779,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

View file

@ -359,5 +359,20 @@
"",
"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/acornjs/acorn/blob/master/acorn/LICENSE
// cannot be found by the OSS tool automatically.
"name": "acorn",
"fullLicenseText": [
"MIT License",
"Copyright (C) 2012-2018 by various contributors (see AUTHORS)",
"",
"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."
]
}
]

View file

@ -53,7 +53,7 @@ export function activate(
new TypeScriptVersion(
TypeScriptVersionSource.Bundled,
vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript-web/tsserver.web.js').toString(),
API.v400));
API.fromSimpleString('4.0.3')));
const lazyClientHost = createLazyClientHost(context, false, {
pluginManager,

View file

@ -9,7 +9,7 @@ import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export default class API {
private static fromSimpleString(value: string): API {
public static fromSimpleString(value: string): API {
return new API(value, value, value);
}

View file

@ -30,24 +30,23 @@
dependencies:
mkdirp "^1.0.4"
"@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==
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@types/glob@*":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
dependencies:
"@types/events" "*"
"@types/minimatch" "*"
"@types/node" "*"
"@types/json-schema@^7.0.4":
version "7.0.5"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
"@types/json-schema@^7.0.5":
version "7.0.6"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
"@types/minimatch@*":
version "3.0.3"
@ -55,14 +54,14 @@
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*":
version "13.1.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.6.tgz#076028d0b0400be8105b89a0a55550c86684ffec"
integrity "sha1-B2Ao0LBAC+gQW4mgpVVQyGaE/+w= sha512-Jg1F+bmxcpENHP23sVKkNuU3uaxPnsBMW0cLjleiikFKomJQbsn0Cqk2yDvQArqzZN6ABfBkZ0To7pQ8sLdWDg=="
version "14.11.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256"
integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==
"@types/node@^12.11.7":
version "12.12.24"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.24.tgz#d4606afd8cf6c609036b854360367d1b2c78931f"
integrity "sha1-1GBq/Yz2xgkDa4VDYDZ9Gyx4kx8= sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug=="
version "12.12.62"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.62.tgz#733923d73669188d35950253dd18a21570085d2b"
integrity sha512-qAfo81CsD7yQIM9mVyh6B/U47li5g7cfpVQEDMfQeF8pSZVwzbhwU3crc0qG4DmpsebpJPR49AKOExQyJ05Cpg==
"@types/rimraf@2.0.2":
version "2.0.2"
@ -84,39 +83,36 @@ agent-base@4, agent-base@^4.3.0:
dependencies:
es6-promisify "^5.0.0"
agent-base@6:
version "6.0.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4"
integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==
dependencies:
debug "4"
aggregate-error@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0"
integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
dependencies:
clean-stack "^2.0.0"
indent-string "^4.0.0"
ajv-keywords@^3.4.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.1.tgz#b83ca89c5d42d69031f424cad49aada0236c6957"
integrity sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==
ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
ajv@^6.12.2:
version "6.12.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706"
integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==
ajv@^6.12.4:
version "6.12.5"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^6.5.5:
version "6.10.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
applicationinsights@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
@ -131,45 +127,11 @@ array-union@^2.1.0:
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
dependencies:
safer-buffer "~2.1.0"
assert-plus@1.0.0, assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c"
integrity "sha1-JDkOatYThrCnRyZXVNKhchnehiw= sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A=="
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
dependencies:
tweetnacl "^0.14.3"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@ -200,7 +162,7 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
cacache@^15.0.4:
cacache@^15.0.5:
version "15.0.5"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0"
integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==
@ -223,11 +185,6 @@ cacache@^15.0.4:
tar "^6.0.2"
unique-filename "^1.1.1"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
@ -238,13 +195,6 @@ clean-stack@^2.0.0:
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@2.15.1:
version "2.15.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
@ -266,34 +216,22 @@ concat-map@0.0.1:
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
copy-webpack-plugin@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.0.3.tgz#2b3d2bfc6861b96432a65f0149720adbd902040b"
integrity sha512-q5m6Vz4elsuyVEIUXr7wJdIdePWTubsqVbEMvf1WQnHGv0Q+9yPRu7MtYFPt+GBOXRav9lvIINifTQ1vSCs+eA==
version "6.1.1"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.1.1.tgz#737a3ba21c16cc6ddca972f5cf8de25568ca0616"
integrity sha512-4TlkHFYkrZ3WppLA5XkPmBLI5lnEpFsXvpeqxCf5PzkratZiVklNXsvoQkLhUU43q7ZL3AOXtaHAd9jLNJoU0w==
dependencies:
cacache "^15.0.4"
cacache "^15.0.5"
fast-glob "^3.2.4"
find-cache-dir "^3.3.1"
glob-parent "^5.1.1"
globby "^11.0.1"
loader-utils "^2.0.0"
normalize-path "^3.0.0"
p-limit "^3.0.1"
schema-utils "^2.7.0"
serialize-javascript "^4.0.0"
p-limit "^3.0.2"
schema-utils "^2.7.1"
serialize-javascript "^5.0.1"
webpack-sources "^1.4.3"
core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
dependencies:
assert-plus "^1.0.0"
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
@ -301,6 +239,13 @@ debug@3.1.0:
dependencies:
ms "2.0.0"
debug@4:
version "4.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
dependencies:
ms "2.1.2"
debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@ -308,11 +253,6 @@ debug@^3.1.0:
dependencies:
ms "^2.1.1"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
diagnostic-channel-publishers@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
@ -337,14 +277,6 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
@ -367,26 +299,6 @@ escape-string-regexp@1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -407,7 +319,7 @@ fast-glob@^3.1.1, fast-glob@^3.2.4:
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity "sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM= sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fastq@^1.6.0:
version "1.8.0"
@ -440,20 +352,6 @@ find-up@^4.0.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@ -466,13 +364,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
dependencies:
assert-plus "^1.0.0"
glob-parent@^5.1.0, glob-parent@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
@ -521,19 +412,6 @@ growl@1.10.5:
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
har-schema@^2.0.0:
version "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:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
dependencies:
ajv "^6.5.5"
har-schema "^2.0.0"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@ -552,14 +430,14 @@ http-proxy-agent@^2.1.0:
agent-base "4"
debug "3.1.0"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
http-proxy-agent@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
dependencies:
assert-plus "^1.0.0"
jsprim "^1.2.2"
sshpk "^1.7.0"
"@tootallnate/once" "1"
agent-base "6"
debug "4"
https-proxy-agent@^2.2.1:
version "2.2.4"
@ -569,6 +447,14 @@ https-proxy-agent@^2.2.1:
agent-base "^4.3.0"
debug "^3.1.0"
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"
ignore@^5.1.4:
version "5.1.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
@ -619,36 +505,11 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
json5@^2.1.2:
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
@ -657,19 +518,9 @@ json5@^2.1.2:
minimist "^1.2.5"
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"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
dependencies:
assert-plus "1.0.0"
extsprintf "1.3.0"
json-schema "0.2.3"
verror "1.10.0"
version "2.3.1"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==
loader-utils@^2.0.0:
version "2.0.0"
@ -714,18 +565,6 @@ micromatch@^4.0.2:
braces "^3.0.1"
picomatch "^2.0.5"
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
integrity "sha1-ChLgUCZQ5HPXNVNQUOfI9OtPrlg= sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
mime-types@^2.1.12, mime-types@~2.1.19:
version "2.1.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
integrity "sha1-nJIfwJt+FJpl39wNpNIJlyALCgY= sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ=="
dependencies:
mime-db "1.43.0"
minimatch@3.0.4, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@ -758,9 +597,9 @@ minipass-flush@^1.0.5:
minipass "^3.0.0"
minipass-pipeline@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz#55f7839307d74859d6e8ada9c3ebe72cec216a34"
integrity sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ==
version "1.2.4"
resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c"
integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==
dependencies:
minipass "^3.0.0"
@ -771,10 +610,10 @@ minipass@^3.0.0, minipass@^3.1.1:
dependencies:
yallist "^4.0.0"
minizlib@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3"
integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
dependencies:
minipass "^3.0.0"
yallist "^4.0.0"
@ -813,7 +652,7 @@ 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.2, 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==
@ -823,11 +662,6 @@ normalize-path@^3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@ -842,7 +676,7 @@ p-limit@^2.2.0:
dependencies:
p-try "^2.0.0"
p-limit@^3.0.1:
p-limit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe"
integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==
@ -883,11 +717,6 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picomatch@^2.0.5, picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
@ -905,31 +734,11 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
psl@^1.1.24:
version "1.7.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c"
integrity "sha1-8cTEeo75cWfepda79IFtc26ISjw= 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:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
querystringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@ -937,37 +746,6 @@ randombytes@^2.1.0:
dependencies:
safe-buffer "^5.1.0"
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==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
caseless "~0.12.0"
combined-stream "~1.0.6"
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.0"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
oauth-sign "~0.9.0"
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.4.3"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@ -992,29 +770,19 @@ run-parallel@^1.1.9:
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
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:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
schema-utils@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
schema-utils@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
dependencies:
"@types/json-schema" "^7.0.4"
ajv "^6.12.2"
ajv-keywords "^3.4.1"
"@types/json-schema" "^7.0.5"
ajv "^6.12.4"
ajv-keywords "^3.5.2"
semver@5.5.1:
version "5.5.1"
@ -1031,10 +799,10 @@ semver@^6.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
serialize-javascript@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
serialize-javascript@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
dependencies:
randombytes "^2.1.0"
@ -1048,15 +816,7 @@ source-list-map@^2.0.0:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
source-map-support@^0.5.0:
version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-support@~0.5.12:
source-map-support@^0.5.0, source-map-support@~0.5.12:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
@ -1069,21 +829,6 @@ source-map@^0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
bcrypt-pbkdf "^1.0.0"
dashdash "^1.12.0"
ecc-jsbn "~0.1.1"
getpass "^0.1.1"
jsbn "~0.1.0"
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
ssri@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808"
@ -1099,14 +844,14 @@ supports-color@5.4.0:
has-flag "^3.0.0"
tar@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39"
integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==
version "6.0.5"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f"
integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^3.0.0"
minizlib "^2.1.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"
@ -1126,26 +871,6 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.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==
dependencies:
psl "^1.1.24"
punycode "^1.4.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"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
typescript-vscode-sh-plugin@^0.6.14:
version "0.6.14"
resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.14.tgz#a81031b502f6346a26ea49ce082438c3e353bb38"
@ -1153,7 +878,7 @@ typescript-vscode-sh-plugin@^0.6.14:
"typescript-web-server@git://github.com/mjbvz/ts-server-web-build":
version "0.0.0"
resolved "git://github.com/mjbvz/ts-server-web-build#1d85be25043f9b5e36a531941ea345dd5a2ca007"
resolved "git://github.com/mjbvz/ts-server-web-build#2a70d88432760118a6aab21da7b819a57e7d4e5e"
unique-filename@^1.1.1:
version "1.1.1"
@ -1170,34 +895,12 @@ unique-slug@^2.0.0:
imurmurhash "^0.1.4"
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
version "4.4.0"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602"
integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==
dependencies:
punycode "^2.1.0"
url-parse@^1.4.4:
version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
uuid@^3.3.2:
version "3.3.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
verror@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
dependencies:
assert-plus "^1.0.0"
core-util-is "1.0.2"
extsprintf "^1.2.0"
vscode-extension-telemetry@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b"
@ -1206,9 +909,9 @@ vscode-extension-telemetry@0.1.1:
applicationinsights "1.0.8"
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==
version "4.1.2"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==
vscode-test@^0.4.1:
version "0.4.3"
@ -1219,16 +922,16 @@ vscode-test@^0.4.1:
https-proxy-agent "^2.2.1"
vscode@^1.1.36:
version "1.1.36"
resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6"
integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ==
version "1.1.37"
resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.37.tgz#c2a770bee4bb3fff765e2b72c7bcc813b8a6bb0a"
integrity sha512-vJNj6IlN7IJPdMavlQa1KoFB3Ihn06q1AiN3ZFI/HfzPNzbKZWPPuiU+XkpNOfGU5k15m4r80nxNPlM7wcc0wg==
dependencies:
glob "^7.1.2"
http-proxy-agent "^4.0.1"
https-proxy-agent "^5.0.0"
mocha "^5.2.0"
request "^2.88.0"
semver "^5.4.1"
source-map-support "^0.5.0"
url-parse "^1.4.4"
vscode-test "^0.4.1"
webpack-sources@^1.4.3:

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.50.0",
"distro": "f4fbb2133880d47be366fb94ea9d149862bddaf3",
"distro": "122f46af8ad9c977be8bb3dc7f898c08cc8a8f28",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -123,7 +123,7 @@
"webBuiltInExtensions": [
{
"name": "ms-vscode.github-browser",
"version": "0.0.8",
"version": "0.0.9",
"repo": "https://github.com/microsoft/vscode-github-browser",
"metadata": {
"id": "c1bcff4b-4ecb-466e-b8f6-b02788b5fb5a",

View file

@ -13,7 +13,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { Schemas, FileAccess, RemoteAuthorities } from 'vs/base/common/network';
import { FileAccess, RemoteAuthorities } from 'vs/base/common/network';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
export function clearNode(node: HTMLElement): void {
@ -1219,18 +1219,6 @@ export function animate(fn: () => void): IDisposable {
RemoteAuthorities.setPreferredWebSchema(/^https:/.test(window.location.href) ? 'https' : 'http');
export function asDomUri(uri: URI): URI {
if (!uri) {
return uri;
}
if (uri.scheme === Schemas.vscodeRemote) {
return RemoteAuthorities.rewrite(uri);
}
return FileAccess.asBrowserUri(uri);
}
/**
* returns url('...')
*/
@ -1238,7 +1226,7 @@ export function asCSSUrl(uri: URI): string {
if (!uri) {
return `url('')`;
}
return `url('${asDomUri(uri).toString(true).replace(/'/g, '%27')}')`;
return `url('${FileAccess.asBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`;
}

View file

@ -14,7 +14,7 @@ import { parse } from 'vs/base/common/marshalling';
import { cloneAndChange } from 'vs/base/common/objects';
import { escape } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
import { resolvePath } from 'vs/base/common/resources';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
@ -70,7 +70,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
// 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);
return FileAccess.asBrowserUri(uri).toString(true);
}
if (uri.query) {
uri = uri.with({ query: _uriMassage(uri.query) });

View file

@ -277,8 +277,8 @@ export class Dialog extends Disposable {
if (this.styles) {
const style = this.styles;
const fgColor = style.dialogForeground ? `${style.dialogForeground}` : '';
const bgColor = style.dialogBackground ? `${style.dialogBackground}` : '';
const fgColor = style.dialogForeground;
const bgColor = style.dialogBackground;
const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : '';
const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : '';
@ -287,8 +287,8 @@ export class Dialog extends Disposable {
}
if (this.element) {
this.element.style.color = fgColor;
this.element.style.backgroundColor = bgColor;
this.element.style.color = fgColor?.toString() ?? '';
this.element.style.backgroundColor = bgColor?.toString() ?? '';
this.element.style.border = border;
if (this.buttonGroup) {
@ -300,8 +300,8 @@ export class Dialog extends Disposable {
}
if (this.messageDetailElement && fgColor && bgColor) {
const messageDetailColor = Color.fromHex(fgColor).transparent(.9);
this.messageDetailElement.style.color = messageDetailColor.makeOpaque(Color.fromHex(bgColor)).toString();
const messageDetailColor = fgColor.transparent(.9);
this.messageDetailElement.style.color = messageDetailColor.makeOpaque(bgColor).toString();
}
if (this.iconElement) {

View file

@ -482,6 +482,7 @@ export namespace Codicon {
export const magnet = new Codicon('magnet', { character: '\\ebae' });
export const notebook = new Codicon('notebook', { character: '\\ebaf' });
export const redo = new Codicon('redo', { character: '\\ebb0' });
export const checkAll = new Codicon('check-all', { character: '\\ebb1' });
}

View file

@ -141,6 +141,10 @@ class FileAccessImpl {
asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
const uri = this.toUri(uriOrModule, moduleIdToUrl);
if (uri.scheme === Schemas.vscodeRemote) {
return RemoteAuthorities.rewrite(uri);
}
return uri;
}

View file

@ -66,6 +66,9 @@ import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService';
import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc';
import { ActiveWindowManager } from 'vs/platform/windows/electron-sandbox/windowTracker';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@ -156,8 +159,11 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const nativeHostService = createChannelSender<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: configuration.windowId });
services.set(INativeHostService, nativeHostService);
const activeWindowManager = new ActiveWindowManager(nativeHostService);
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
services.set(IDownloadService, new SyncDescriptor(DownloadService));
services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(server.getChannel('IExtensionRecommendationNotificationService', activeWindowRouter)));
const instantiationService = new InstantiationService(services);

View file

@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import product from 'vs/platform/product/common/product';
import { IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } 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';
@ -85,9 +85,6 @@ const enum ReadyState {
export class CodeWindow extends Disposable implements ICodeWindow {
private static readonly MIN_WIDTH = 600;
private static readonly MIN_HEIGHT = 270;
private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
private readonly _onLoad = this._register(new Emitter<void>());
@ -162,8 +159,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
x: this.windowState.x,
y: this.windowState.y,
backgroundColor: this.themeMainService.getBackgroundColor(),
minWidth: CodeWindow.MIN_WIDTH,
minHeight: CodeWindow.MIN_HEIGHT,
minWidth: WindowMinimumSize.WIDTH,
minHeight: WindowMinimumSize.HEIGHT,
show: !isFullscreenOrMaximized,
title: product.nameLong,
webPreferences: {

View file

@ -1495,11 +1495,13 @@ export interface CommentThread {
comments: Comment[] | undefined;
onDidChangeComments: Event<Comment[] | undefined>;
collapsibleState?: CommentThreadCollapsibleState;
readOnly: boolean;
input?: CommentInput;
onDidChangeInput: Event<CommentInput | undefined>;
onDidChangeRange: Event<IRange>;
onDidChangeLabel: Event<string | undefined>;
onDidChangeCollasibleState: Event<CommentThreadCollapsibleState | undefined>;
onDidChangeReadOnly: Event<boolean>;
isDisposed: boolean;
}

View file

@ -86,7 +86,7 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P
return;
}
if (token.count > MAX_FILES) {
if (token.count >= MAX_FILES) {
token.count += files.length;
token.maxReached = true;
resolve();

View file

@ -4,18 +4,28 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { join, } from 'vs/base/common/path';
import { basename, join, } from 'vs/base/common/path';
import { IProductService } from 'vs/platform/product/common/productService';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { IFileService } from 'vs/platform/files/common/files';
import { isWindows } from 'vs/base/common/platform';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
import { forEach } from 'vs/base/common/collections';
import { IExecutableBasedExtensionTip, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { forEach, IStringDictionary } from 'vs/base/common/collections';
import { IRequestService } from 'vs/platform/request/common/request';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtensionTipsService as BaseExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService';
import { timeout } from 'vs/base/common/async';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { localize } from 'vs/nls';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
type ExeExtensionRecommendationsClassification = {
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
exeName: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
type IExeBasedExtensionTips = {
readonly exeFriendlyName: string,
@ -23,6 +33,8 @@ type IExeBasedExtensionTips = {
readonly recommendations: { extensionId: string, extensionName: string, isExtensionPack: boolean }[];
};
const promptedExecutableTipsStorageKey = 'extensionTips/promptedExecutableTips';
export class ExtensionTipsService extends BaseExtensionTipsService {
_serviceBrand: any;
@ -32,6 +44,10 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
constructor(
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionRecommendationNotificationService private readonly extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
@IFileService fileService: IFileService,
@IProductService productService: IProductService,
@IRequestService requestService: IRequestService,
@ -57,6 +73,12 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
}
});
}
/*
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.promptImportantExeBasedRecommendations());
}
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
@ -67,6 +89,77 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
return this.getValidExecutableBasedExtensionTips(this.allOtherExecutableTips);
}
private async promptImportantExeBasedRecommendations(): Promise<void> {
const importantExeBasedRecommendations = new Map<string, IExecutableBasedExtensionTip>();
const importantExeBasedTips = await this.getImportantExecutableBasedTips();
importantExeBasedTips.forEach(tip => importantExeBasedRecommendations.set(tip.extensionId.toLowerCase(), tip));
const local = await this.extensionManagementService.getInstalled();
const { installed, uninstalled: recommendations } = this.groupByInstalled([...importantExeBasedRecommendations.keys()], local);
/* Log installed and uninstalled exe based recommendations */
for (const extensionId of installed) {
const tip = importantExeBasedRecommendations.get(extensionId);
if (tip) {
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
}
}
for (const extensionId of recommendations) {
const tip = importantExeBasedRecommendations.get(extensionId);
if (tip) {
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:notInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
}
}
const recommendationsByExe = new Map<string, IExecutableBasedExtensionTip[]>();
const promptedExecutableTips = this.getPromptedExecutableTips();
for (const extensionId of recommendations) {
const tip = importantExeBasedRecommendations.get(extensionId);
if (tip && (!promptedExecutableTips[tip.exeName] || !promptedExecutableTips[tip.exeName].includes(tip.extensionId))) {
let tips = recommendationsByExe.get(tip.exeName);
if (!tips) {
tips = [];
recommendationsByExe.set(tip.exeName, tips);
}
tips.push(tip);
}
}
for (const [, tips] of recommendationsByExe) {
const extensionIds = tips.map(({ extensionId }) => extensionId.toLowerCase());
const message = localize('exeRecommended', "You have {0} installed on your system. Do you want to install the recommended extensions for it?", tips[0].exeFriendlyName);
this.extensionRecommendationNotificationService.promptImportantExtensionsInstallNotification(extensionIds, message, `@exe:"${tips[0].exeName}"`)
.then(result => {
if (result) {
this.addToRecommendedExecutables(tips[0].exeName, extensionIds);
}
});
}
}
private getPromptedExecutableTips(): IStringDictionary<string[]> {
return JSON.parse(this.storageService.get(promptedExecutableTipsStorageKey, StorageScope.GLOBAL, '{}'));
}
private addToRecommendedExecutables(exeName: string, extensions: string[]) {
const promptedExecutableTips = this.getPromptedExecutableTips();
promptedExecutableTips[exeName] = extensions;
this.storageService.store(promptedExecutableTipsStorageKey, JSON.stringify(promptedExecutableTips), StorageScope.GLOBAL);
}
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<string>());
recommendationsToSuggest.forEach(id => {
if (installedExtensionsIds.has(id.toLowerCase())) {
installed.push(id);
} else {
uninstalled.push(id);
}
});
return { installed, uninstalled };
}
private async getValidExecutableBasedExtensionTips(executableTips: Map<string, IExeBasedExtensionTips>): Promise<IExecutableBasedExtensionTip[]> {
const result: IExecutableBasedExtensionTip[] = [];

View file

@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IExtensionRecommendationNotificationService = createDecorator<IExtensionRecommendationNotificationService>('IExtensionRecommendationNotificationService');
export interface IExtensionRecommendationNotificationService {
readonly _serviceBrand: undefined;
readonly ignoredRecommendations: string[];
hasToIgnoreRecommendationNotifications(): boolean;
promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string): Promise<boolean>;
promptWorkspaceRecommendations(recommendations: string[]): Promise<boolean>;
}

View file

@ -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 { Event } from 'vs/base/common/event';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
export class ExtensionRecommendationNotificationServiceChannelClient implements IExtensionRecommendationNotificationService {
declare readonly _serviceBrand: undefined;
constructor(private readonly channel: IChannel) { }
get ignoredRecommendations(): string[] { throw new Error('not supported'); }
promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string): Promise<boolean> {
return this.channel.call('promptImportantExtensionsInstallNotification', [extensionIds, message, searchValue]);
}
promptWorkspaceRecommendations(recommendations: string[]): Promise<boolean> {
throw new Error('not supported');
}
hasToIgnoreRecommendationNotifications(): boolean {
throw new Error('not supported');
}
}
export class ExtensionRecommendationNotificationServiceChannel implements IServerChannel {
constructor(private service: IExtensionRecommendationNotificationService) { }
listen(_: unknown, event: string): Event<any> {
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, args?: any): Promise<any> {
switch (command) {
case 'promptImportantExtensionsInstallNotification': return this.service.promptImportantExtensionsInstallNotification(args[0], args[1], args[2]);
}
throw new Error(`Call not found: ${command}`);
}
}

View file

@ -67,6 +67,8 @@ export interface ICommonNativeHostService {
unmaximizeWindow(): Promise<void>;
minimizeWindow(): Promise<void>;
setMinimumSize(width: number | undefined, height: number | undefined): Promise<void>;
/**
* Make the window focused.
*

View file

@ -211,6 +211,23 @@ export class NativeHostMainService implements INativeHostMainService {
}
}
async setMinimumSize(windowId: number | undefined, width: number | undefined, height: number | undefined): Promise<void> {
const window = this.windowById(windowId);
if (window) {
const [windowWidth, windowHeight] = window.win.getSize();
const [minWindowWidth, minWindowHeight] = window.win.getMinimumSize();
const [newMinWindowWidth, newMinWindowHeight] = [width ?? minWindowWidth, height ?? minWindowHeight];
const [newWindowWidth, newWindowHeight] = [Math.max(windowWidth, newMinWindowWidth), Math.max(windowHeight, newMinWindowHeight)];
if (minWindowWidth !== newMinWindowWidth || minWindowHeight !== newMinWindowHeight) {
window.win.setMinimumSize(newMinWindowWidth, newMinWindowHeight);
}
if (windowWidth !== newWindowWidth || windowHeight !== newWindowHeight) {
window.win.setSize(newWindowWidth, newWindowHeight);
}
}
}
//#endregion
//#region Dialog

View file

@ -13,6 +13,12 @@ import { LogLevel } from 'vs/platform/log/common/log';
import { ExportData } from 'vs/base/common/performance';
import { ColorScheme } from 'vs/platform/theme/common/theme';
export const WindowMinimumSize = {
WIDTH: 400,
WIDTH_WITH_VERTICAL_PANEL: 600,
HEIGHT: 270
};
export interface IBaseOpenWindowsOptions {
forceReuseWindow?: boolean;
}

View file

@ -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 { Event } from 'vs/base/common/event';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
export class ActiveWindowManager extends Disposable {
private readonly disposables = this._register(new DisposableStore());
private firstActiveWindowIdPromise: CancelablePromise<number | undefined> | undefined;
private activeWindowId: number | undefined;
constructor(@INativeHostService nativeHostService: INativeHostService) {
super();
// remember last active window id upon events
const onActiveWindowChange = Event.latch(Event.any(nativeHostService.onWindowOpen, nativeHostService.onWindowFocus));
onActiveWindowChange(this.setActiveWindow, this, this.disposables);
// resolve current active window
this.firstActiveWindowIdPromise = createCancelablePromise(() => nativeHostService.getActiveWindowId());
(async () => {
try {
const windowId = await this.firstActiveWindowIdPromise;
this.activeWindowId = (typeof this.activeWindowId === 'number') ? this.activeWindowId : windowId;
} finally {
this.firstActiveWindowIdPromise = undefined;
}
})();
}
private setActiveWindow(windowId: number | undefined) {
if (this.firstActiveWindowIdPromise) {
this.firstActiveWindowIdPromise.cancel();
this.firstActiveWindowIdPromise = undefined;
}
this.activeWindowId = windowId;
}
async getActiveClientId(): Promise<string | undefined> {
const id = this.firstActiveWindowIdPromise ? (await this.firstActiveWindowIdPromise) : this.activeWindowId;
return `window:${id}`;
}
}

View file

@ -2155,4 +2155,14 @@ declare module 'vscode' {
constructor(id: string, color?: ThemeColor);
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/102665 Comment API @rebornix
export interface CommentThread {
/**
* Whether the thread supports reply.
* Defaults to false.
*/
readOnly: boolean;
}
//#endregion
}

View file

@ -84,6 +84,17 @@ export class MainThreadCommentThread implements modes.CommentThread {
return this._range;
}
private readonly _onDidChangeReadOnly = new Emitter<boolean>();
get onDidChangeReadOnly(): Event<boolean> { return this._onDidChangeReadOnly.event; }
set readOnly(state: boolean) {
this._readOnly = state;
this._onDidChangeReadOnly.fire(this._readOnly);
}
get readOnly() {
return this._readOnly;
}
private readonly _onDidChangeRange = new Emitter<IRange>();
public onDidChangeRange = this._onDidChangeRange.event;
@ -112,7 +123,8 @@ export class MainThreadCommentThread implements modes.CommentThread {
public extensionId: string,
public threadId: string,
public resource: string,
private _range: IRange
private _range: IRange,
private _readOnly: boolean
) {
this._isDisposed = false;
}
@ -126,6 +138,7 @@ export class MainThreadCommentThread implements modes.CommentThread {
if (modified('contextValue')) { this._contextValue = changes.contextValue; }
if (modified('comments')) { this._comments = changes.comments; }
if (modified('collapseState')) { this._collapsibleState = changes.collapseState; }
if (modified('readOnly')) { this.readOnly = changes.readOnly!; }
}
dispose() {
@ -214,7 +227,8 @@ export class MainThreadCommentController {
extensionId,
threadId,
URI.revive(resource).toString(),
range
range,
false
);
this._threads.set(commentThreadHandle, thread);

View file

@ -303,6 +303,10 @@ export class MainThreadSCM implements MainThreadSCMShape {
setTimeout(() => this._proxy.$setSelectedSourceControl(handle), 0);
}
if (repository.input.value) {
setTimeout(() => this._proxy.$onInputBoxValueChange(handle, repository.input.value), 0);
}
this._repositoryDisposables.set(handle, disposable);
}

View file

@ -144,6 +144,7 @@ export type CommentThreadChanges = Partial<{
contextValue: string,
comments: modes.Comment[],
collapseState: modes.CommentThreadCollapsibleState;
readOnly: boolean;
}>;
export interface MainThreadCommentsShape extends IDisposable {

View file

@ -219,6 +219,7 @@ type CommentThreadModification = Partial<{
contextValue: string | undefined,
comments: vscode.Comment[],
collapsibleState: vscode.CommentThreadCollapsibleState
readOnly: boolean;
}>;
export class ExtHostCommentThread implements vscode.CommentThread {
@ -263,6 +264,19 @@ export class ExtHostCommentThread implements vscode.CommentThread {
return this._range;
}
private _readonly: boolean = false;
set readOnly(state: boolean) {
if (this._readonly !== state) {
this._readonly = state;
this.modifications.readOnly = state;
this._onDidUpdateCommentThread.fire();
}
}
get readOnly() {
return this._readonly;
}
private _label: string | undefined;
get label(): string | undefined {
@ -387,6 +401,9 @@ export class ExtHostCommentThread implements vscode.CommentThread {
if (modified('collapsibleState')) {
formattedModifications.collapseState = convertToCollapsibleState(this._collapseState);
}
if (modified('readOnly')) {
formattedModifications.readOnly = this.readOnly;
}
this.modifications = {};
this._proxy.$updateCommentThread(

View file

@ -506,7 +506,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
const host: IExtensionActivationHost = {
folders: folders.map(folder => folder.uri),
forceUsingSearch: localWithRemote,
exists: (path) => this._hostUtils.exists(path),
exists: (uri) => this._hostUtils.exists(uri.fsPath),
checkExists: (folders, includes, token) => this._mainThreadWorkspaceProxy.$checkExists(folders, includes, token)
};

View file

@ -338,7 +338,9 @@ export namespace MarkdownString {
}
export function to(value: htmlContent.IMarkdownString): vscode.MarkdownString {
return new htmlContent.MarkdownString(value.value, { isTrusted: value.isTrusted, supportThemeIcons: value.supportThemeIcons });
const result = new types.MarkdownString(value.value, value.supportThemeIcons);
result.isTrusted = value.isTrusted;
return result;
}
export function fromStrict(value: string | types.MarkdownString): undefined | string | htmlContent.IMarkdownString {

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import * as resources from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
@ -19,7 +19,7 @@ export interface IExtensionActivationHost {
readonly folders: readonly UriComponents[];
readonly forceUsingSearch: boolean;
exists(path: string): Promise<boolean>;
exists(uri: URI): Promise<boolean>;
checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
}
@ -69,7 +69,7 @@ export function checkActivateWorkspaceContainsExtension(host: IExtensionActivati
async function _activateIfFileName(host: IExtensionActivationHost, fileName: string, activate: (activationEvent: string) => void): Promise<void> {
// find exact path
for (const uri of host.folders) {
if (await host.exists(path.join(URI.revive(uri).fsPath, fileName))) {
if (await host.exists(resources.joinPath(URI.revive(uri), fileName))) {
// the file was found
activate(`workspaceContains:${fileName}`);
return;

View file

@ -10,7 +10,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
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 { FileAccess, Schemas } from 'vs/base/common/network';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
@ -22,7 +22,7 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor';
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 { addDisposableListener, EventType } 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';
@ -322,7 +322,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
// Download URL: enables support to drag a tab as file to desktop (only single file supported)
// Disabled for PWA web due to: https://github.com/microsoft/vscode/issues/83441
if (!sources[0].isDirectory && (!isWeb || !isStandalone)) {
event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(sources[0].resource), asDomUri(sources[0].resource).toString()].join(':'));
event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(sources[0].resource), FileAccess.asBrowserUri(sources[0].resource).toString()].join(':'));
}
// Resource URLs: allows to drop multiple resources to a target in VS Code (not directories)

View file

@ -1307,7 +1307,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
[titleBar, editorPart, activityBar, panelPart, sideBar, statusBar].forEach((part: Part) => {
this._register(part.onDidVisibilityChange((visible) => {
this._onPartVisibilityChange.fire();
if (part === sideBar) {
this.setSideBarHidden(!visible, true);
} else if (part === panelPart) {
@ -1315,6 +1314,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
} else if (part === editorPart) {
this.setEditorHidden(!visible, true);
}
this._onPartVisibilityChange.fire();
}));
});
@ -1543,6 +1543,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
setPanelHidden(hidden: boolean, skipLayout?: boolean): void {
const wasHidden = this.state.panel.hidden;
this.state.panel.hidden = hidden;
// Return if not initialized fully #105480
@ -1581,21 +1582,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.toggleMaximizedPanel();
}
// Propagate layout changes to grid
if (!skipLayout) {
this.workbenchGrid.setViewVisible(this.panelPartView, !hidden);
// If in process of showing, toggle whether or not panel is maximized
if (!hidden) {
if (isPanelMaximized !== panelOpensMaximized) {
this.toggleMaximizedPanel();
}
}
else {
// If in process of hiding, remember whether the panel is maximized or not
this.state.panel.wasLastMaximized = isPanelMaximized;
}
// Don't proceed if we have already done this before
if (wasHidden === hidden) {
return;
}
// Propagate layout changes to grid
this.workbenchGrid.setViewVisible(this.panelPartView, !hidden);
// If in process of showing, toggle whether or not panel is maximized
if (!hidden) {
if (isPanelMaximized !== panelOpensMaximized) {
this.toggleMaximizedPanel();
}
}
else {
// If in process of hiding, remember whether the panel is maximized or not
this.state.panel.wasLastMaximized = isPanelMaximized;
}
// Remember in settings
if (!hidden) {
this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE);

View file

@ -61,10 +61,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
protected _actionbarWidget!: ActionBar;
private _bodyElement!: HTMLElement;
private _parentEditor: ICodeEditor;
private _commentEditor!: ICodeEditor;
private _commentsElement!: HTMLElement;
private _commentElements: CommentNode[] = [];
private _commentForm!: HTMLElement;
private _commentReplyComponent?: {
editor: ICodeEditor;
form: HTMLElement;
commentEditorIsEmpty: IContextKey<boolean>;
};
private _reviewThreadReplyButton!: HTMLElement;
private _resizeObserver: any;
private readonly _onDidClose = new Emitter<ReviewZoneWidget | undefined>();
@ -82,7 +85,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
private _contextKeyService: IContextKeyService;
private _threadIsEmpty: IContextKey<boolean>;
private _commentThreadContextValue: IContextKey<string>;
private _commentEditorIsEmpty!: IContextKey<boolean>;
private _commentFormActions!: CommentFormActions;
private _scopedInstatiationService: IInstantiationService;
private _focusedComment: number | undefined = undefined;
@ -199,8 +201,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
public getPendingComment(): string | null {
if (this._commentEditor) {
let model = this._commentEditor.getModel();
if (this._commentReplyComponent) {
let model = this._commentReplyComponent.editor.getModel();
if (model && model.getValueLength() > 0) { // checking length is cheap
return model.getValue();
@ -367,8 +369,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}
if (!this._reviewThreadReplyButton) {
this.createReplyButton();
if (!this._reviewThreadReplyButton && this._commentReplyComponent) {
this.createReplyButton(this._commentReplyComponent.editor, this._commentReplyComponent.form);
}
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
@ -400,11 +402,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
protected _onWidth(widthInPixel: number): void {
this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
this._commentReplyComponent?.editor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
}
protected _doLayout(heightInPixel: number, widthInPixel: number): void {
this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
this._commentReplyComponent?.editor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
}
display(lineNumber: number) {
@ -448,52 +450,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}
const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
this._commentForm = dom.append(this._bodyElement, dom.$('.comment-form'));
this._commentEditor = this._scopedInstatiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this);
this._commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
this._commentEditorIsEmpty.set(!this._pendingComment);
const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID);
const params = JSON.stringify({
extensionId: this.extensionId,
commentThreadId: this.commentThread.threadId
});
let resource = URI.parse(`${COMMENT_SCHEME}://${this.extensionId}/commentinput-${modeId}.md?${params}`); // TODO. Remove params once extensions adopt authority.
let commentController = this.commentService.getCommentController(this.owner);
if (commentController) {
resource = resource.with({ authority: commentController.id });
// create comment thread only when not readonly
if (!this._commentThread.readOnly) {
this.createCommentForm();
}
const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource), resource, false);
this._disposables.add(model);
this._commentEditor.setModel(model);
this._disposables.add(this._commentEditor);
this._disposables.add(this._commentEditor.getModel()!.onDidChangeContent(() => {
this.setCommentEditorDecorations();
this._commentEditorIsEmpty.set(!this._commentEditor.getValue());
}));
this.createTextModelListener();
this.setCommentEditorDecorations();
// Only add the additional step of clicking a reply button to expand the textarea when there are existing comments
if (hasExistingComments) {
this.createReplyButton();
} else {
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
this.expandReplyArea();
}
}
this._error = dom.append(this._commentForm, dom.$('.validation-error.hidden'));
this._formActions = dom.append(this._commentForm, dom.$('.form-actions'));
this.createCommentWidgetActions(this._formActions, model);
this.createCommentWidgetActionsListener();
this._resizeObserver = new MutationObserver(this._refresh.bind(this));
this._resizeObserver.observe(this._bodyElement, {
@ -509,25 +470,94 @@ 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) {
this._commentEditor.focus();
} else if (this._commentEditor.getModel()!.getValueLength() > 0) {
this.expandReplyArea();
if (!this._commentThread.readOnly && this._commentReplyComponent) {
if (!this._commentThread.comments || !this._commentThread.comments.length) {
this._commentReplyComponent.editor.focus();
} else if (this._commentReplyComponent.editor.getModel()!.getValueLength() > 0) {
this.expandReplyArea();
}
}
this._commentThreadDisposables.push(this._commentThread.onDidChangeReadOnly(() => {
if (this._commentReplyComponent) {
if (this._commentThread.readOnly) {
this._commentReplyComponent.form.style.display = 'none';
} else {
this._commentReplyComponent.form.style.display = 'block';
}
} else {
if (!this._commentThread.readOnly) {
this.createCommentForm();
}
}
}));
}
private createTextModelListener() {
this._commentThreadDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => {
private createCommentForm() {
const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
const commentForm = dom.append(this._bodyElement, dom.$('.comment-form'));
const commentEditor = this._scopedInstatiationService.createInstance(SimpleCommentEditor, commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this);
const commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
commentEditorIsEmpty.set(!this._pendingComment);
this._commentReplyComponent = {
form: commentForm,
editor: commentEditor,
commentEditorIsEmpty
};
const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID);
const params = JSON.stringify({
extensionId: this.extensionId,
commentThreadId: this.commentThread.threadId
});
let resource = URI.parse(`${COMMENT_SCHEME}://${this.extensionId}/commentinput-${modeId}.md?${params}`); // TODO. Remove params once extensions adopt authority.
let commentController = this.commentService.getCommentController(this.owner);
if (commentController) {
resource = resource.with({ authority: commentController.id });
}
const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource), resource, false);
this._disposables.add(model);
commentEditor.setModel(model);
this._disposables.add(commentEditor);
this._disposables.add(commentEditor.getModel()!.onDidChangeContent(() => {
this.setCommentEditorDecorations();
commentEditorIsEmpty?.set(!commentEditor.getValue());
}));
this.createTextModelListener(commentEditor, commentForm);
this.setCommentEditorDecorations();
// Only add the additional step of clicking a reply button to expand the textarea when there are existing comments
if (hasExistingComments) {
this.createReplyButton(commentEditor, commentForm);
} else {
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
this.expandReplyArea();
}
}
this._error = dom.append(commentForm, dom.$('.validation-error.hidden'));
this._formActions = dom.append(commentForm, dom.$('.form-actions'));
this.createCommentWidgetActions(this._formActions, model);
this.createCommentWidgetActionsListener();
}
private createTextModelListener(commentEditor: ICodeEditor, commentForm: HTMLElement) {
this._commentThreadDisposables.push(commentEditor.onDidFocusEditorWidget(() => {
this._commentThread.input = {
uri: this._commentEditor.getModel()!.uri,
value: this._commentEditor.getValue()
uri: commentEditor.getModel()!.uri,
value: commentEditor.getValue()
};
this.commentService.setActiveCommentThread(this._commentThread);
}));
this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => {
let modelContent = this._commentEditor.getValue();
if (this._commentThread.input && this._commentThread.input.uri === this._commentEditor.getModel()!.uri && this._commentThread.input.value !== modelContent) {
this._commentThreadDisposables.push(commentEditor.getModel()!.onDidChangeContent(() => {
let modelContent = commentEditor.getValue();
if (this._commentThread.input && this._commentThread.input.uri === commentEditor.getModel()!.uri && this._commentThread.input.value !== modelContent) {
let newInput: modes.CommentInput = this._commentThread.input;
newInput.value = modelContent;
this._commentThread.input = newInput;
@ -538,20 +568,20 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._commentThreadDisposables.push(this._commentThread.onDidChangeInput(input => {
let thread = this._commentThread;
if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) {
if (thread.input && thread.input.uri !== commentEditor.getModel()!.uri) {
return;
}
if (!input) {
return;
}
if (this._commentEditor.getValue() !== input.value) {
this._commentEditor.setValue(input.value);
if (commentEditor.getValue() !== input.value) {
commentEditor.setValue(input.value);
if (input.value === '') {
this._pendingComment = '';
this._commentForm.classList.remove('expand');
this._commentEditor.getDomNode()!.style.outline = '';
commentForm.classList.remove('expand');
commentEditor.getDomNode()!.style.outline = '';
this._error.textContent = '';
this._error.classList.add('hidden');
}
@ -639,7 +669,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
action.run({
thread: this._commentThread,
text: this._commentEditor.getValue(),
text: this._commentReplyComponent?.editor.getValue(),
$mid: 8
});
@ -696,23 +726,25 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
private expandReplyArea() {
if (!this._commentForm.classList.contains('expand')) {
this._commentForm.classList.add('expand');
this._commentEditor.focus();
if (!this._commentReplyComponent?.form.classList.contains('expand')) {
this._commentReplyComponent?.form.classList.add('expand');
this._commentReplyComponent?.editor.focus();
}
}
private hideReplyArea() {
this._commentEditor.setValue('');
if (this._commentReplyComponent) {
this._commentReplyComponent.editor.setValue('');
this._commentReplyComponent.editor.getDomNode()!.style.outline = '';
}
this._pendingComment = '';
this._commentForm.classList.remove('expand');
this._commentEditor.getDomNode()!.style.outline = '';
this._commentReplyComponent?.form.classList.remove('expand');
this._error.textContent = '';
this._error.classList.add('hidden');
}
private createReplyButton() {
this._reviewThreadReplyButton = <HTMLButtonElement>dom.append(this._commentForm, dom.$(`button.review-thread-reply-button.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`));
private createReplyButton(commentEditor: ICodeEditor, commentForm: HTMLElement) {
this._reviewThreadReplyButton = <HTMLButtonElement>dom.append(commentForm, dom.$(`button.review-thread-reply-button.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`));
this._reviewThreadReplyButton.title = this._commentOptions?.prompt || nls.localize('reply', "Reply...");
this._reviewThreadReplyButton.textContent = this._commentOptions?.prompt || nls.localize('reply', "Reply...");
@ -720,9 +752,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea()));
this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea()));
this._commentEditor.onDidBlurEditorWidget(() => {
if (this._commentEditor.getModel()!.getValueLength() === 0 && this._commentForm.classList.contains('expand')) {
this._commentForm.classList.remove('expand');
commentEditor.onDidBlurEditorWidget(() => {
if (commentEditor.getModel()!.getValueLength() === 0 && commentForm.classList.contains('expand')) {
commentForm.classList.remove('expand');
}
});
}
@ -753,7 +785,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
if (!this._commentThread.comments || !this._commentThread.comments.length) {
this._commentEditor.focus();
this._commentReplyComponent?.editor.focus();
}
this._relayout(computedLinesNumber);
@ -761,7 +793,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
private setCommentEditorDecorations() {
const model = this._commentEditor && this._commentEditor.getModel();
const model = this._commentReplyComponent && this._commentReplyComponent.editor.getModel();
if (model) {
const valueLength = model.getValueLength();
const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
@ -785,7 +817,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}];
this._commentEditor.setDecorations(COMMENTEDITOR_DECORATION_KEY, decorations);
this._commentReplyComponent?.editor.setDecorations(COMMENTEDITOR_DECORATION_KEY, decorations);
}
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IExtensionTipsService, IConfigBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { localize } from 'vs/nls';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IWorkspaceContextService, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
@ -27,11 +27,10 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
constructor(
promptedExtensionRecommendations: PromptedExtensionRecommendations,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
) {
super(promptedExtensionRecommendations);
super();
}
protected async doActivate(): Promise<void> {

View file

@ -11,7 +11,7 @@ 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, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { localize } from 'vs/nls';
@ -30,7 +30,6 @@ export class DynamicWorkspaceRecommendations extends ExtensionRecommendations {
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
constructor(
promptedExtensionRecommendations: PromptedExtensionRecommendations,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@ -38,7 +37,7 @@ export class DynamicWorkspaceRecommendations extends ExtensionRecommendations {
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IStorageService private readonly storageService: IStorageService,
) {
super(promptedExtensionRecommendations);
super();
}
protected async doActivate(): Promise<void> {

View file

@ -3,20 +3,11 @@
* 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, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { timeout } from 'vs/base/common/async';
import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { localize } from 'vs/nls';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { basename } from 'vs/base/common/path';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
type ExeExtensionRecommendationsClassification = {
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
exeName: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
export class ExeBasedRecommendations extends ExtensionRecommendations {
@ -28,23 +19,10 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
private readonly tasExperimentService: ITASExperimentService | undefined;
constructor(
promptedExtensionRecommendations: PromptedExtensionRecommendations,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@optional(ITASExperimentService) tasExperimentService: ITASExperimentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
) {
super(promptedExtensionRecommendations);
this.tasExperimentService = tasExperimentService;
/*
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());
super();
}
getRecommendations(exe: string): { important: ExtensionRecommendation[], others: ExtensionRecommendation[] } {
@ -79,75 +57,6 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
return importantExeBasedRecommendations;
}
private async fetchAndPromptImportantExeBasedRecommendations(): Promise<void> {
const importantExeBasedRecommendations = await this.fetchImportantExeBasedRecommendations();
const local = await this.extensionManagementService.getInstalled();
const { installed, uninstalled } = this.groupByInstalled([...importantExeBasedRecommendations.keys()], local);
/* Log installed and uninstalled exe based recommendations */
for (const extensionId of installed) {
const tip = importantExeBasedRecommendations.get(extensionId);
if (tip) {
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
}
}
for (const extensionId of uninstalled) {
const tip = importantExeBasedRecommendations.get(extensionId);
if (tip) {
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:notInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
}
}
this.promptImportantExeBasedRecommendations(uninstalled, importantExeBasedRecommendations);
}
private async promptImportantExeBasedRecommendations(recommendations: string[], importantExeBasedRecommendations: Map<string, IExecutableBasedExtensionTip>): Promise<void> {
if (this.promptedExtensionRecommendations.hasToIgnoreRecommendationNotifications()) {
return;
}
recommendations = this.promptedExtensionRecommendations.filterIgnoredOrNotAllowed(recommendations);
if (recommendations.length === 0) {
return;
}
const recommendationsByExe = new Map<string, IExecutableBasedExtensionTip[]>();
for (const extensionId of recommendations) {
const tip = importantExeBasedRecommendations.get(extensionId);
if (tip) {
let tips = recommendationsByExe.get(tip.exeFriendlyName);
if (!tips) {
tips = [];
recommendationsByExe.set(tip.exeFriendlyName, tips);
}
tips.push(tip);
}
}
for (const [, tips] of recommendationsByExe) {
const extensionIds = tips.map(({ extensionId }) => extensionId.toLowerCase());
if (this.tasExperimentService && extensionIds.indexOf('ms-vscode-remote.remote-wsl') !== -1) {
await this.tasExperimentService.getTreatment<boolean>('wslpopupaa');
}
const message = localize('exeRecommended', "You have {0} installed on your system. Do you want to install the recommended extensions for it?", tips[0].exeFriendlyName);
this.promptedExtensionRecommendations.promptImportantExtensionsInstallNotification(extensionIds, message, `@exe:"${tips[0].exeName}"`);
}
}
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<string>());
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(),

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService';
@ -14,10 +14,9 @@ export class ExperimentalRecommendations extends ExtensionRecommendations {
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
constructor(
promptedExtensionRecommendations: PromptedExtensionRecommendations,
@IExperimentService private readonly experimentService: IExperimentService,
) {
super(promptedExtensionRecommendations);
super();
}
/**

View file

@ -19,7 +19,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
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 { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
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';
@ -189,6 +189,7 @@ export class ExtensionEditor extends EditorPane {
@INotificationService private readonly notificationService: INotificationService,
@IOpenerService private readonly openerService: IOpenerService,
@IExtensionRecommendationsService private readonly extensionRecommendationsService: IExtensionRecommendationsService,
@IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService,
@IStorageService storageService: IStorageService,
@IExtensionService private readonly extensionService: IExtensionService,
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
@ -471,36 +472,27 @@ export class ExtensionEditor extends EditorPane {
this.transientDisposables.add(ignoreAction);
this.transientDisposables.add(undoIgnoreAction);
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.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);
}
else {
template.subtext.textContent = '';
}
this.extensionRecommendationsService.onRecommendationChange(change => {
if (change.extensionId.toLowerCase() === extension.identifier.id.toLowerCase()) {
if (change.isRecommended) {
undoIgnoreAction.enabled = false;
const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason();
if (extRecommendations[extension.identifier.id.toLowerCase()]) {
ignoreAction.enabled = true;
template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
}
} else {
undoIgnoreAction.enabled = true;
ignoreAction.enabled = false;
template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
}
const updateRecommendationFn = () => {
const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason();
if (extRecommendations[extension.identifier.id.toLowerCase()]) {
ignoreAction.enabled = true;
undoIgnoreAction.enabled = false;
template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
show(template.subtextContainer);
} else if (this.extensionIgnoredRecommendationsService.globalIgnoredRecommendations.indexOf(extension.identifier.id.toLowerCase()) !== -1) {
ignoreAction.enabled = false;
undoIgnoreAction.enabled = true;
template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
show(template.subtextContainer);
} else {
ignoreAction.enabled = false;
undoIgnoreAction.enabled = false;
template.subtext.textContent = '';
hide(template.subtextContainer);
}
});
};
updateRecommendationFn();
this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationFn()));
this.transientDisposables.add(reloadAction.onDidChange(e => {
if (e.tooltip) {

View file

@ -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 { IAction } from 'vs/base/common/actions';
import { distinct } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
import { EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
interface IExtensionsConfiguration {
autoUpdate: boolean;
autoCheckUpdates: boolean;
ignoreRecommendations: boolean;
showRecommendationsOnlyOnDemand: boolean;
closeExtensionDetailsOnViewChange: boolean;
}
type ExtensionRecommendationsNotificationClassification = {
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
extensionId?: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
type ExtensionWorkspaceRecommendationsNotificationClassification = {
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
const ignoreImportantExtensionRecommendationStorageKey = 'extensionsAssistant/importantRecommendationsIgnore';
const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore';
const choiceNever = localize('neverShowAgain', "Don't Show Again");
export class ExtensionRecommendationNotificationService implements IExtensionRecommendationNotificationService {
declare readonly _serviceBrand: undefined;
private readonly tasExperimentService: ITASExperimentService | undefined;
// Ignored Important Recommendations
get ignoredRecommendations(): string[] {
return distinct([...(<string[]>JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendationStorageKey, StorageScope.GLOBAL, '[]')))].map(i => i.toLowerCase()));
}
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStorageService private readonly storageService: IStorageService,
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
@IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
@optional(ITASExperimentService) tasExperimentService: ITASExperimentService,
) {
storageKeysSyncRegistryService.registerStorageKey({ key: ignoreImportantExtensionRecommendationStorageKey, version: 1 });
this.tasExperimentService = tasExperimentService;
}
hasToIgnoreRecommendationNotifications(): boolean {
const config = this.configurationService.getValue<IExtensionsConfiguration>('extensions');
return config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand;
}
async promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string): Promise<boolean> {
if (this.hasToIgnoreRecommendationNotifications()) {
return false;
}
const ignoredRecommendations = [...this.extensionIgnoredRecommendationsService.ignoredRecommendations, ...this.ignoredRecommendations];
extensionIds = extensionIds.filter(id => !ignoredRecommendations.includes(id));
if (!extensionIds.length) {
return false;
}
const extensions = await this.getInstallableExtensions(extensionIds);
if (!extensions.length) {
return false;
}
if (this.tasExperimentService && extensionIds.indexOf('ms-vscode-remote.remote-wsl') !== -1) {
await this.tasExperimentService.getTreatment<boolean>('wslpopupaa');
}
return new Promise<boolean>((c, e) => {
let cancelled: boolean = false;
const handle = this.notificationService.prompt(Severity.Info, message,
[{
label: localize('install', "Install"),
run: async () => {
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
await Promise.all(extensions.map(async extension => {
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId: extension.identifier.id });
this.extensionsWorkbenchService.open(extension, { pinned: true });
await this.extensionManagementService.installFromGallery(extension.gallery!);
}));
}
}, {
label: localize('show recommendations', "Show Recommendations"),
run: async () => {
for (const extension of extensions) {
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId: extension.identifier.id });
this.extensionsWorkbenchService.open(extension, { pinned: true });
}
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
}
}, {
label: choiceNever,
isSecondary: true,
run: () => {
for (const extension of extensions) {
this.addToImportantRecommendationsIgnore(extension.identifier.id);
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: extension.identifier.id });
}
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: () => {
cancelled = true;
for (const extension of extensions) {
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: extension.identifier.id });
}
}
}
);
const disposable = handle.onDidClose(() => {
disposable.dispose();
c(!cancelled);
});
});
}
async promptWorkspaceRecommendations(recommendations: string[]): Promise<boolean> {
if (this.hasToIgnoreWorkspaceRecommendationNotifications()) {
return false;
}
let installed = await this.extensionManagementService.getInstalled();
installed = installed.filter(l => this.extensionEnablementService.getEnablementState(l) !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind
recommendations = recommendations.filter(extensionId => installed.every(local => !areSameExtensions({ id: extensionId }, local.identifier)));
if (!recommendations.length) {
return false;
}
const extensions = await this.getInstallableExtensions(recommendations);
if (!extensions.length) {
return false;
}
const searchValue = '@recommended ';
this.notificationService.prompt(
Severity.Info,
localize('workspaceRecommended', "Do you want to install the recommended extensions for this repository?"),
[{
label: localize('install', "Install"),
run: async () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'install' });
await Promise.all(extensions.map(async extension => {
this.extensionsWorkbenchService.open(extension, { pinned: true });
await this.extensionManagementService.installFromGallery(extension.gallery!);
}));
}
}, {
label: localize('showRecommendations', "Show Recommendations"),
run: async () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'show' });
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
}
}, {
label: localize('neverShowAgain', "Don't Show Again"),
isSecondary: true,
run: () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' });
this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE);
}
}],
{
sticky: true,
onCancel: () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' });
}
}
);
return true;
}
private hasToIgnoreWorkspaceRecommendationNotifications(): boolean {
return this.hasToIgnoreRecommendationNotifications() || this.storageService.getBoolean(ignoreWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, false);
}
private async getInstallableExtensions(extensionIds: string[]): Promise<IExtension[]> {
const extensions: IExtension[] = [];
if (extensionIds.length) {
const pager = await this.extensionsWorkbenchService.queryGallery({ names: extensionIds, pageSize: extensionIds.length, source: 'install-recommendations' }, CancellationToken.None);
for (const extension of pager.firstPage) {
if (extension.gallery && (await this.extensionManagementService.canInstall(extension.gallery))) {
extensions.push(extension);
}
}
}
return extensions;
}
private async runAction(action: IAction): Promise<void> {
try {
await action.run();
} finally {
action.dispose();
}
}
private addToImportantRecommendationsIgnore(id: string) {
const importantRecommendationsIgnoreList = [...this.ignoredRecommendations];
if (!importantRecommendationsIgnoreList.includes(id.toLowerCase())) {
importantRecommendationsIgnoreList.push(id.toLowerCase());
this.storageService.store(ignoreImportantExtensionRecommendationStorageKey, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL);
}
}
private setIgnoreRecommendationsConfig(configVal: boolean) {
this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER);
}
}

View file

@ -4,34 +4,7 @@
*--------------------------------------------------------------------------------------------*/
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 { SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationReson } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IExtensionsConfiguration, ConfigurationKey, IExtension, IExtensionsWorkbenchService } 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';
import { IAction } from 'vs/base/common/actions';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { CancellationToken } from 'vs/base/common/cancellation';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
type ExtensionRecommendationsNotificationClassification = {
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
extensionId?: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
type ExtensionWorkspaceRecommendationsNotificationClassification = {
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore';
const ignoreImportantExtensionRecommendation = 'extensionsAssistant/importantRecommendationsIgnore';
const choiceNever = localize('neverShowAgain', "Don't Show Again");
export type ExtensionRecommendation = {
readonly extensionId: string,
@ -43,12 +16,6 @@ export abstract class ExtensionRecommendations extends Disposable {
readonly abstract recommendations: ReadonlyArray<ExtensionRecommendation>;
protected abstract doActivate(): Promise<void>;
constructor(
protected readonly promptedExtensionRecommendations: PromptedExtensionRecommendations,
) {
super();
}
private _activationPromise: Promise<void> | null = null;
get activated(): boolean { return this._activationPromise !== null; }
activate(): Promise<void> {
@ -59,193 +26,3 @@ export abstract class ExtensionRecommendations extends Disposable {
}
}
export class PromptedExtensionRecommendations extends Disposable {
constructor(
private readonly isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super();
storageKeysSyncRegistryService.registerStorageKey({ key: ignoreImportantExtensionRecommendation, version: 1 });
}
async promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string): Promise<void> {
if (this.hasToIgnoreRecommendationNotifications()) {
return;
}
const extensions = await this.getInstallableExtensions(extensionIds);
if (!extensions.length) {
return;
}
this.notificationService.prompt(Severity.Info, message,
[{
label: localize('install', "Install"),
run: async () => {
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
await Promise.all(extensions.map(async extension => {
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId: extension.identifier.id });
this.extensionsWorkbenchService.open(extension, { pinned: true });
await this.extensionManagementService.installFromGallery(extension.gallery!);
}));
}
}, {
label: localize('show recommendations', "Show Recommendations"),
run: async () => {
for (const extension of extensions) {
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId: extension.identifier.id });
this.extensionsWorkbenchService.open(extension, { pinned: true });
}
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
}
}, {
label: choiceNever,
isSecondary: true,
run: () => {
for (const extension of extensions) {
this.addToImportantRecommendationsIgnore(extension.identifier.id);
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: extension.identifier.id });
}
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: () => {
for (const extension of extensions) {
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: extension.identifier.id });
}
}
}
);
}
async promptWorkspaceRecommendations(recommendations: string[]): Promise<void> {
if (this.hasToIgnoreWorkspaceRecommendationNotifications()) {
return;
}
let installed = await this.extensionManagementService.getInstalled();
installed = installed.filter(l => this.extensionEnablementService.getEnablementState(l) !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind
recommendations = recommendations.filter(extensionId => installed.every(local => !areSameExtensions({ id: extensionId }, local.identifier)));
if (!recommendations.length) {
return;
}
const extensions = await this.getInstallableExtensions(recommendations);
if (!extensions.length) {
return;
}
const searchValue = '@recommended ';
this.notificationService.prompt(
Severity.Info,
localize('workspaceRecommended', "Do you want to install the recommended extensions for this repository?"),
[{
label: localize('install', "Install"),
run: async () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'install' });
await Promise.all(extensions.map(async extension => {
this.extensionsWorkbenchService.open(extension, { pinned: true });
await this.extensionManagementService.installFromGallery(extension.gallery!);
}));
}
}, {
label: localize('showRecommendations', "Show Recommendations"),
run: async () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'show' });
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
}
}, {
label: localize('neverShowAgain', "Don't Show Again"),
isSecondary: true,
run: () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' });
this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE);
}
}],
{
sticky: true,
onCancel: () => {
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' });
}
}
);
}
hasToIgnoreRecommendationNotifications(): boolean {
const config = this.configurationService.getValue<IExtensionsConfiguration>(ConfigurationKey);
return config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand;
}
hasToIgnoreWorkspaceRecommendationNotifications(): boolean {
return this.hasToIgnoreRecommendationNotifications() || this.storageService.getBoolean(ignoreWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, false);
}
filterIgnoredOrNotAllowed(recommendationsToSuggest: string[]): string[] {
const importantRecommendationsIgnoreList = (<string[]>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 async getInstallableExtensions(extensionIds: string[]): Promise<IExtension[]> {
const extensions: IExtension[] = [];
if (extensionIds.length) {
const pager = await this.extensionsWorkbenchService.queryGallery({ names: extensionIds, pageSize: extensionIds.length, source: 'install-recommendations' }, CancellationToken.None);
for (const extension of pager.firstPage) {
if (extension.gallery && (await this.extensionManagementService.canInstall(extension.gallery))) {
extensions.push(extension);
}
}
}
return extensions;
}
private async runAction(action: IAction): Promise<void> {
try {
await action.run();
} finally {
action.dispose();
}
}
private addToImportantRecommendationsIgnore(id: string) {
const importantRecommendationsIgnoreList = <string[]>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);
}
}

View file

@ -5,8 +5,7 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { IExtensionManagementService, IExtensionGalleryService, InstallOperation, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationsService, ExtensionRecommendationReason, RecommendationChangeNotification } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { IExtensionRecommendationsService, ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
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';
@ -21,23 +20,19 @@ import { ExperimentalRecommendations } from 'vs/workbench/contrib/extensions/bro
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, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ConfigBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/configBasedRecommendations';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
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 {
declare readonly _serviceBrand: undefined;
private readonly promptedExtensionRecommendations: PromptedExtensionRecommendations;
// Recommendations
private readonly fileBasedRecommendations: FileBasedRecommendations;
private readonly workspaceRecommendations: WorkspaceRecommendations;
@ -47,39 +42,32 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
private readonly dynamicWorkspaceRecommendations: DynamicWorkspaceRecommendations;
private readonly keymapRecommendations: KeymapRecommendations;
// Ignored Recommendations
private globallyIgnoredRecommendations: string[] = [];
public readonly activationPromise: Promise<void>;
private sessionSeed: number;
private readonly _onRecommendationChange = this._register(new Emitter<RecommendationChangeNotification>());
onRecommendationChange: Event<RecommendationChangeNotification> = this._onRecommendationChange.event;
private _onDidChangeRecommendations = this._register(new Emitter<void>());
readonly onDidChangeRecommendations = this._onDidChangeRecommendations.event;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@ILifecycleService private readonly 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,
@IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService,
@IExtensionRecommendationNotificationService private readonly extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
) {
super();
storageKeysSyncRegistryService.registerStorageKey({ key: ignoredRecommendationsStorageKey, version: 1 });
const isExtensionAllowedToBeRecommended = (extensionId: string) => this.isExtensionAllowedToBeRecommended(extensionId);
this.promptedExtensionRecommendations = instantiationService.createInstance(PromptedExtensionRecommendations, isExtensionAllowedToBeRecommended);
this.workspaceRecommendations = instantiationService.createInstance(WorkspaceRecommendations, this.promptedExtensionRecommendations);
this.fileBasedRecommendations = instantiationService.createInstance(FileBasedRecommendations, this.promptedExtensionRecommendations);
this.experimentalRecommendations = instantiationService.createInstance(ExperimentalRecommendations, this.promptedExtensionRecommendations);
this.configBasedRecommendations = instantiationService.createInstance(ConfigBasedRecommendations, this.promptedExtensionRecommendations);
this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations, this.promptedExtensionRecommendations);
this.dynamicWorkspaceRecommendations = instantiationService.createInstance(DynamicWorkspaceRecommendations, this.promptedExtensionRecommendations);
this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations, this.promptedExtensionRecommendations);
this.workspaceRecommendations = instantiationService.createInstance(WorkspaceRecommendations);
this.fileBasedRecommendations = instantiationService.createInstance(FileBasedRecommendations);
this.experimentalRecommendations = instantiationService.createInstance(ExperimentalRecommendations);
this.configBasedRecommendations = instantiationService.createInstance(ConfigBasedRecommendations);
this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations);
this.dynamicWorkspaceRecommendations = instantiationService.createInstance(DynamicWorkspaceRecommendations);
this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations);
if (!this.isEnabled()) {
this.sessionSeed = 0;
@ -88,13 +76,11 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
}
this.sessionSeed = +new Date();
this.globallyIgnoredRecommendations = this.getCachedIgnoredRecommendations();
// Activation
this.activationPromise = this.activate();
this._register(this.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e)));
this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
}
private async activate(): Promise<void> {
@ -114,6 +100,16 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
})
]);
this._register(this.extensionRecommendationsManagementService.onDidChangeIgnoredRecommendations(() => this._onDidChangeRecommendations.fire()));
this._register(this.extensionRecommendationsManagementService.onDidChangeGlobalIgnoredRecommendation(({ extensionId, isRecommended }) => {
if (!isRecommended) {
const reason = this.getAllRecommendationsWithReason()[extensionId];
if (reason && reason.reasonId) {
this.telemetryService.publicLog2<{ extensionId: string, recommendationReason: ExtensionRecommendationReason }, IgnoreRecommendationClassification>('extensionsRecommendations:ignoreRecommendation', { extensionId, recommendationReason: reason.reasonId });
}
}
}));
await this.promptWorkspaceRecommendations();
this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations)(() => this.promptWorkspaceRecommendations()));
}
@ -217,29 +213,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
return this.toExtensionRecommendations(this.fileBasedRecommendations.recommendations);
}
getIgnoredRecommendations(): ReadonlyArray<string> {
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() || {};
@ -265,12 +238,8 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
return extensionIds;
}
private isExtensionAllowedToBeRecommended(id: string): boolean {
const allIgnoredRecommendations = [
...this.globallyIgnoredRecommendations,
...this.workspaceRecommendations.ignoredRecommendations
];
return allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1;
private isExtensionAllowedToBeRecommended(extensionId: string): boolean {
return !this.extensionRecommendationsManagementService.ignoredRecommendations.includes(extensionId.toLowerCase());
}
private async promptWorkspaceRecommendations(): Promise<void> {
@ -279,49 +248,10 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
.filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId));
if (allowedRecommendations.length) {
await this.promptedExtensionRecommendations.promptWorkspaceRecommendations(allowedRecommendations);
await this.extensionRecommendationNotificationService.promptWorkspaceRecommendations(allowedRecommendations);
}
}
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);
}
}

View file

@ -56,9 +56,12 @@ import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/brow
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { ExtensionRecommendationNotificationService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
registerSingleton(IExtensionRecommendationNotificationService, ExtensionRecommendationNotificationService);
registerSingleton(IExtensionRecommendationsService, ExtensionRecommendationsService);
Registry.as<IOutputChannelRegistry>(OutputExtensions.OutputChannels)

View file

@ -16,7 +16,7 @@ import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IE
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
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, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationsService, IExtensionsConfigContent } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IExtensionIgnoredRecommendationsService, IExtensionsConfigContent } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
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';
@ -1912,7 +1912,7 @@ export class IgnoreExtensionRecommendationAction extends Action {
constructor(
private readonly extension: IExtension,
@IExtensionRecommendationsService private readonly extensionsTipsService: IExtensionRecommendationsService,
@IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService,
) {
super(IgnoreExtensionRecommendationAction.ID, 'Ignore Recommendation');
@ -1922,7 +1922,7 @@ export class IgnoreExtensionRecommendationAction extends Action {
}
public run(): Promise<any> {
this.extensionsTipsService.toggleIgnoredRecommendation(this.extension.identifier.id, true);
this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation(this.extension.identifier.id, true);
return Promise.resolve();
}
}
@ -1935,7 +1935,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action {
constructor(
private readonly extension: IExtension,
@IExtensionRecommendationsService private readonly extensionsTipsService: IExtensionRecommendationsService,
@IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService,
) {
super(UndoIgnoreExtensionRecommendationAction.ID, 'Undo');
@ -1945,7 +1945,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action {
}
public run(): Promise<any> {
this.extensionsTipsService.toggleIgnoredRecommendation(this.extension.identifier.id, false);
this.extensionRecommendationsManagementService.toggleGlobalIgnoredRecommendation(this.extension.identifier.id, false);
return Promise.resolve();
}
}

View file

@ -955,7 +955,7 @@ export class DefaultRecommendedExtensionsView extends ExtensionsListView {
renderBody(container: HTMLElement): void {
super.renderBody(container);
this._register(this.extensionRecommendationsService.onRecommendationChange(() => {
this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => {
this.show('');
}));
}
@ -980,7 +980,7 @@ export class RecommendedExtensionsView extends ExtensionsListView {
renderBody(container: HTMLElement): void {
super.renderBody(container);
this._register(this.extensionRecommendationsService.onRecommendationChange(() => {
this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => {
this.show('');
}));
}
@ -997,7 +997,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
renderBody(container: HTMLElement): void {
super.renderBody(container);
this._register(this.extensionRecommendationsService.onRecommendationChange(() => this.show(this.recommendedExtensionsQuery)));
this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => this.show(this.recommendedExtensionsQuery)));
this._register(this.contextService.onDidChangeWorkbenchState(() => this.show(this.recommendedExtensionsQuery)));
}

View file

@ -199,7 +199,7 @@ export class RecommendationWidget extends ExtensionWidget {
super();
this.render();
this._register(toDisposable(() => this.clear()));
this._register(this.extensionRecommendationsService.onRecommendationChange(() => this.render()));
this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => this.render()));
}
private clear(): void {

View file

@ -36,7 +36,6 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IProductService } from 'vs/platform/product/common/productService';
import { asDomUri } from 'vs/base/browser/dom';
import { getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge';
import { isWeb } from 'vs/base/common/platform';
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
@ -135,7 +134,7 @@ class Extension implements IExtension {
private get localIconUrl(): string | null {
if (this.local && this.local.manifest.icon) {
return asDomUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);
return FileAccess.asBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);
}
return null;
}

View file

@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
@ -25,12 +25,14 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IModelService } from 'vs/editor/common/services/modelService';
import { setImmediate } from 'vs/base/common/platform';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
type FileExtensionSuggestionClassification = {
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
fileExtension: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
const promptedRecommendationsStorageKey = 'fileBasedRecommendations/promptedRecommendations';
const recommendationsStorageKey = 'extensionsAssistant/recommendations';
const searchMarketplace = localize('searchMarketplace', "Search Marketplace");
const milliSecondsInADay = 1000 * 60 * 60 * 24;
@ -81,7 +83,6 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
}
constructor(
promptedExtensionRecommendations: PromptedExtensionRecommendations,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionService private readonly extensionService: IExtensionService,
@IViewletService private readonly viewletService: IViewletService,
@ -91,8 +92,10 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionRecommendationNotificationService private readonly extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
@IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService,
) {
super(promptedExtensionRecommendations);
super();
if (productService.extensionTips) {
forEach(productService.extensionTips, ({ key, value }) => this.extensionTips.set(key.toLowerCase(), value));
@ -204,13 +207,13 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
this.storeCachedRecommendations();
if (this.promptedExtensionRecommendations.hasToIgnoreRecommendationNotifications()) {
if (this.extensionRecommendationNotificationService.hasToIgnoreRecommendationNotifications()) {
return;
}
const installed = await this.extensionsWorkbenchService.queryLocal();
if (importantRecommendations.length &&
await this.promptRecommendedExtensionForFileType(languageName || basename(uri), importantRecommendations, installed)) {
await this.promptRecommendedExtensionForFileType(languageName || basename(uri), language, importantRecommendations, installed)) {
return;
}
@ -227,9 +230,9 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
this.promptRecommendedExtensionForFileExtension(fileExtension, installed);
}
private async promptRecommendedExtensionForFileType(name: string, recommendations: string[], installed: IExtension[]): Promise<boolean> {
private async promptRecommendedExtensionForFileType(name: string, language: string, recommendations: string[], installed: IExtension[]): Promise<boolean> {
recommendations = this.promptedExtensionRecommendations.filterIgnoredOrNotAllowed(recommendations);
recommendations = this.filterIgnoredOrNotAllowed(recommendations);
if (recommendations.length === 0) {
return false;
}
@ -245,10 +248,30 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
return false;
}
this.promptedExtensionRecommendations.promptImportantExtensionsInstallNotification([extensionId], localize('reallyRecommended', "Do you want to install the recommended extensions for {0}?", name), `@id:${extensionId}`);
const promptedRecommendations = this.getPromptedRecommendations();
if (promptedRecommendations[language] && promptedRecommendations[language].includes(extensionId)) {
return false;
}
this.extensionRecommendationNotificationService.promptImportantExtensionsInstallNotification([extensionId], localize('reallyRecommended', "Do you want to install the recommended extensions for {0}?", name), `@id:${extensionId}`)
.then(result => {
if (result) {
this.addToPromptedRecommendations(language, [extensionId]);
}
});
return true;
}
private getPromptedRecommendations(): IStringDictionary<string[]> {
return JSON.parse(this.storageService.get(promptedRecommendationsStorageKey, StorageScope.GLOBAL, '{}'));
}
private addToPromptedRecommendations(exeName: string, extensions: string[]) {
const promptedRecommendations = this.getPromptedRecommendations();
promptedRecommendations[exeName] = extensions;
this.storageService.store(promptedRecommendationsStorageKey, JSON.stringify(promptedRecommendations), StorageScope.GLOBAL);
}
private async promptRecommendedExtensionForFileExtension(fileExtension: string, installed: IExtension[]): Promise<void> {
const fileExtensionSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]'));
if (fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1) {
@ -301,6 +324,11 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
);
}
private filterIgnoredOrNotAllowed(recommendationsToSuggest: string[]): string[] {
const ignoredRecommendations = [...this.extensionIgnoredRecommendationsService.ignoredRecommendations, ...this.extensionRecommendationNotificationService.ignoredRecommendations];
return recommendationsToSuggest.filter(id => !ignoredRecommendations.includes(id));
}
private filterInstalled(recommendationsToSuggest: string[], installed: IExtension[]): string[] {
const installedExtensionsIds = installed.reduce((result, i) => {
if (i.enablementState !== EnablementState.DisabledByExtensionKind) {

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { IProductService } from 'vs/platform/product/common/productService';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
@ -13,10 +13,9 @@ export class KeymapRecommendations extends ExtensionRecommendations {
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
constructor(
promptedExtensionRecommendations: PromptedExtensionRecommendations,
@IProductService private readonly productService: IProductService,
) {
super(promptedExtensionRecommendations);
super();
}
protected async doActivate(): Promise<void> {

View file

@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { EXTENSION_IDENTIFIER_PATTERN, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkspaceContextService, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { distinct, flatten } from 'vs/base/common/arrays';
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IExtensionsConfigContent, ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { ILogService } from 'vs/platform/log/common/log';
@ -27,19 +26,17 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
get ignoredRecommendations(): ReadonlyArray<string> { return this._ignoredRecommendations; }
constructor(
promptedExtensionRecommendations: PromptedExtensionRecommendations,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IWorkpsaceExtensionsConfigService private readonly workpsaceExtensionsConfigService: IWorkpsaceExtensionsConfigService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@ILogService private readonly logService: ILogService,
@INotificationService private readonly notificationService: INotificationService,
) {
super(promptedExtensionRecommendations);
super();
}
protected async doActivate(): Promise<void> {
await this.fetch();
this._register(this.contextService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e)));
this._register(this.workpsaceExtensionsConfigService.onDidChangeExtensionsConfigs(() => this.onDidChangeExtensionsConfigs()));
}
/**
@ -116,14 +113,12 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
return { validRecommendations: validExtensions, invalidRecommendations: invalidExtensions, message };
}
private async onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): Promise<void> {
if (event.added.length) {
const oldWorkspaceRecommended = this._recommendations;
await this.fetch();
// 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._onDidChangeRecommendations.fire();
}
private async onDidChangeExtensionsConfigs(): Promise<void> {
const oldWorkspaceRecommended = this._recommendations;
await this.fetch();
// 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._onDidChangeRecommendations.fire();
}
}

View file

@ -23,6 +23,9 @@ import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsActions';
import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { ExtensionRecommendationNotificationServiceChannel } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc';
// Singletons
registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true);
@ -64,8 +67,11 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ShowRuntimeExte
class ExtensionsContributions implements IWorkbenchContribution {
constructor(
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
@IExtensionRecommendationNotificationService extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
@ISharedProcessService sharedProcessService: ISharedProcessService,
) {
sharedProcessService.registerChannel('IExtensionRecommendationNotificationService', new ExtensionRecommendationNotificationServiceChannel(extensionRecommendationNotificationService));
if (environmentService.extensionsPath) {
const openExtensionsFolderActionDescriptor = SyncActionDescriptor.from(OpenExtensionsFolderAction);
actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel);

View file

@ -59,6 +59,10 @@ import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkpsaceExtensionsConfigService, WorkspaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { ExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { ExtensionRecommendationNotificationService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService';
const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {
@ -303,6 +307,8 @@ suite('ExtensionRecommendationsService Test', () => {
workspaceService = new TestContextService(myWorkspace);
instantiationService.stub(IWorkspaceContextService, workspaceService);
instantiationService.stub(IWorkpsaceExtensionsConfigService, instantiationService.createInstance(WorkspaceExtensionsConfigService));
instantiationService.stub(IExtensionIgnoredRecommendationsService, instantiationService.createInstance(ExtensionIgnoredRecommendationsService));
instantiationService.stub(IExtensionRecommendationNotificationService, instantiationService.createInstance(ExtensionRecommendationNotificationService));
const fileService = new FileService(new NullLogService());
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()));
instantiationService.stub(IFileService, fileService);
@ -426,73 +432,73 @@ suite('ExtensionRecommendationsService Test', () => {
});
});
test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', () => {
test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => {
const storageService = instantiationService.get(IStorageService);
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);
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL);
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations).then(() => {
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
return testObject.activationPromise.then(() => {
const recommendations = testObject.getAllRecommendationsWithReason();
assert.ok(recommendations['ms-python.python']);
await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations);
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
await testObject.activationPromise;
assert.ok(!recommendations['mockpublisher2.mockextension2']);
assert.ok(!recommendations['ms-dotnettools.csharp']);
});
});
const recommendations = testObject.getAllRecommendationsWithReason();
assert.ok(recommendations['ms-python.python']);
assert.ok(!recommendations['mockpublisher2.mockextension2']);
assert.ok(!recommendations['ms-dotnettools.csharp']);
});
test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', () => {
test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', async () => {
const storageService = instantiationService.get(IStorageService);
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);
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL);
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
return testObject.activationPromise.then(() => {
const recommendations = testObject.getAllRecommendationsWithReason();
assert.ok(recommendations['ms-python.python']);
assert.ok(recommendations['mockpublisher1.mockextension1']);
await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions);
const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
await testObject.activationPromise;
assert.ok(!recommendations['mockpublisher2.mockextension2']);
let recommendations = testObject.getAllRecommendationsWithReason();
assert.ok(recommendations['ms-python.python']);
assert.ok(recommendations['mockpublisher1.mockextension1']);
assert.ok(!recommendations['mockpublisher2.mockextension2']);
return testObject.toggleIgnoredRecommendation('mockpublisher1.mockextension1', true);
}).then(() => {
const recommendations = testObject.getAllRecommendationsWithReason();
assert.ok(recommendations['ms-python.python']);
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', true);
assert.ok(!recommendations['mockpublisher1.mockextension1']);
assert.ok(!recommendations['mockpublisher2.mockextension2']);
recommendations = testObject.getAllRecommendationsWithReason();
assert.ok(recommendations['ms-python.python']);
assert.ok(!recommendations['mockpublisher1.mockextension1']);
assert.ok(!recommendations['mockpublisher2.mockextension2']);
return testObject.toggleIgnoredRecommendation('mockpublisher1.mockextension1', false);
}).then(() => {
const recommendations = testObject.getAllRecommendationsWithReason();
assert.ok(recommendations['ms-python.python']);
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', false);
assert.ok(recommendations['mockpublisher1.mockextension1']);
assert.ok(!recommendations['mockpublisher2.mockextension2']);
});
});
recommendations = testObject.getAllRecommendationsWithReason();
assert.ok(recommendations['ms-python.python']);
assert.ok(recommendations['mockpublisher1.mockextension1']);
assert.ok(!recommendations['mockpublisher2.mockextension2']);
});
test('test global extensions are modified and recommendation change event is fired when an extension is ignored', async () => {
const storageService = instantiationService.get(IStorageService);
const changeHandlerTarget = sinon.spy();
const ignoredExtensionId = 'Some.Extension';
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.GLOBAL);
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
storageService.store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.GLOBAL);
await setUpFolderWorkspace('myFolder', []);
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
testObject.onRecommendationChange(changeHandlerTarget);
testObject.toggleIgnoredRecommendation(ignoredExtensionId, true);
const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);
extensionIgnoredRecommendationsService.onDidChangeGlobalIgnoredRecommendation(changeHandlerTarget);
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation(ignoredExtensionId, true);
await testObject.activationPromise;
assert.ok(changeHandlerTarget.calledOnce);

View file

@ -30,7 +30,7 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Schemas } from 'vs/base/common/network';
import { FileAccess, 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';
@ -39,7 +39,7 @@ import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/e
import { coalesce } from 'vs/base/common/arrays';
import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { getErrorMessage } from 'vs/base/common/errors';
import { triggerDownload, asDomUri } from 'vs/base/browser/dom';
import { triggerDownload } 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';
@ -1015,7 +1015,7 @@ const downloadFileHandler = (accessor: ServicesAccessor) => {
try {
bufferOrUri = (await fileService.readFile(s.resource, { limits: { size: 1024 * 1024 /* set a limit to reduce memory pressure */ } })).value.buffer;
} catch (error) {
bufferOrUri = asDomUri(s.resource);
bufferOrUri = FileAccess.asBrowserUri(s.resource);
}
triggerDownload(bufferOrUri, s.name);

View file

@ -74,7 +74,9 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
) {
super(viewType, model, UUID.generateUuid(), configurationService);
this._register(this.model.onDidChangeOutputs((splices) => {
this._outputCollection = new Array(this.model.outputs.length);
splices.reverse().forEach(splice => {
this._outputCollection.splice(splice[0], splice[1], ...splice[2].map(() => 0));
});
this._outputsTop = null;
this._onDidChangeOutputs.fire(splices);
}));

View file

@ -8,13 +8,15 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, dispose, 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, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, notebookDocumentMetadataDefaults, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, NotebookRawContentEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, notebookDocumentMetadataDefaults, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, NotebookRawContentEvent, IProcessedOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ITextSnapshot } from 'vs/editor/common/model';
import { IUndoRedoService, UndoRedoElementType, IUndoRedoElement, IResourceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
import { MoveCellEdit, SpliceCellsEdit, CellMetadataEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IRange } from 'vs/editor/common/core/range';
import { ISequence, LcsDiff } from 'vs/base/common/diff/diff';
import { hash } from 'vs/base/common/hash';
export class NotebookTextModelSnapshot implements ITextSnapshot {
@ -329,8 +331,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
//TODO@joh,@rebornix no event, no undo stop (?)
this._assertIndex(edit.index);
const cell = this._cells[edit.index];
// TODO@rebornix, we should do diff first
this._spliceNotebookCellOutputs(cell.handle, [[0, cell.outputs.length, edit.outputs]], computeUndoRedo);
this._spliceNotebookCellOutputs2(cell.handle, edit.outputs, computeUndoRedo);
break;
case CellEditType.OutputsSplice:
{
@ -645,6 +646,18 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeLanguage, index: this._cells.indexOf(cell), language: languageId, transient: false }, true);
}
private _spliceNotebookCellOutputs2(cellHandle: number, outputs: IProcessedOutput[], computeUndoRedo: boolean): void {
const cell = this._mapping.get(cellHandle);
if (!cell) {
return;
}
const diff = new LcsDiff(new OutputSequence(cell.outputs), new OutputSequence(outputs));
const diffResult = diff.ComputeDiff(false);
const splices: NotebookCellOutputsSplice[] = diffResult.changes.map(change => [change.originalStart, change.originalLength, outputs.slice(change.modifiedStart, change.modifiedStart + change.modifiedLength)]);
this._spliceNotebookCellOutputs(cellHandle, splices, computeUndoRedo);
}
private _spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[], computeUndoRedo: boolean): void {
const cell = this._mapping.get(cellHandle);
if (cell) {
@ -695,3 +708,22 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
}
}
class OutputSequence implements ISequence {
constructor(readonly outputs: IProcessedOutput[]) {
}
getElements(): Int32Array | number[] | string[] {
return this.outputs.map(output => {
switch (output.outputKind) {
case CellOutputKind.Rich:
return hash([output.outputKind, output.metadata, output.data]);
case CellOutputKind.Error:
return hash([output.outputKind, output.ename, output.evalue, output.traceback]);
case CellOutputKind.Text:
return hash([output.outputKind, output.text]);
}
});
}
}

View file

@ -5,6 +5,7 @@
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/preferences';
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest';
@ -12,18 +13,18 @@ import * as nls from 'vs/nls';
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 { InputFocusedContext, IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys';
import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
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 { Schemas } from 'vs/base/common/network';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
import { AbstractSideBySideEditorInputFactory } from 'vs/workbench/browser/parts/editor/editor.contribution';
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';
@ -31,16 +32,14 @@ import { ExplorerFolderContext, ExplorerRootContext } from 'vs/workbench/contrib
import { KeybindingsEditor } from 'vs/workbench/contrib/preferences/browser/keybindingsEditor';
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, MODIFIED_SETTING_TAG, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences';
import { SettingsEditor2, SettingsFocusContext } 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_ROW_FOCUS, 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_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } 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 { AbstractSideBySideEditorInputFactory } from 'vs/workbench/browser/parts/editor/editor.contribution';
import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search';
@ -51,8 +50,8 @@ const SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING = 'settings.action.editFocuse
const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH = 'settings.action.focusSettingsFromSearch';
const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST = 'settings.action.focusSettingsList';
const SETTINGS_EDITOR_COMMAND_FOCUS_TOC = 'settings.action.focusTOC';
const SETTINGS_EDITOR_COMMAND_FOCUS_TOC2 = 'settings.action.focusTOC2';
const SETTINGS_EDITOR_COMMAND_FOCUS_CONTROL = 'settings.action.focusSettingControl';
const SETTINGS_EDITOR_COMMAND_FOCUS_UP = 'settings.action.focusLevelUp';
const SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON = 'settings.switchToJSON';
const SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED = 'settings.filterByModified';
@ -168,6 +167,8 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
const OPEN_SETTINGS2_ACTION_TITLE = { value: nls.localize('openSettings2', "Open Settings (UI)"), original: 'Open Settings (UI)' };
const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' };
class PreferencesActionsContribution extends Disposable implements IWorkbenchContribution {
constructor(
@ -189,7 +190,6 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
private registerSettingsActions() {
const that = this;
const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' };
registerAction2(class extends Action2 {
constructor() {
super({
@ -226,7 +226,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
id: 'workbench.action.openSettings2',
title: { value: nls.localize('openSettings2', "Open Settings (UI)"), original: 'Open Settings (UI)' },
category,
menu: { id: MenuId.CommandPalette }
f1: true,
});
}
run(accessor: ServicesAccessor) {
@ -239,7 +239,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
id: 'workbench.action.openSettingsJson',
title: { value: nls.localize('openSettingsJson', "Open Settings (JSON)"), original: 'Open Settings (JSON)' },
category,
menu: { id: MenuId.CommandPalette }
f1: true,
});
}
run(accessor: ServicesAccessor) {
@ -252,7 +252,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
id: 'workbench.action.openGlobalSettings',
title: { value: nls.localize('openGlobalSettings', "Open User Settings"), original: 'Open User Settings' },
category,
menu: { id: MenuId.CommandPalette }
f1: true,
});
}
run(accessor: ServicesAccessor) {
@ -265,7 +265,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
id: 'workbench.action.openRawDefaultSettings',
title: { value: nls.localize('openRawDefaultSettings', "Open Default Settings (JSON)"), original: 'Open Default Settings (JSON)' },
category,
menu: { id: MenuId.CommandPalette }
f1: true,
});
}
run(accessor: ServicesAccessor) {
@ -318,7 +318,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
id: ConfigureLanguageBasedSettingsAction.ID,
title: ConfigureLanguageBasedSettingsAction.LABEL,
category,
menu: { id: MenuId.CommandPalette }
f1: true,
});
}
run(accessor: ServicesAccessor) {
@ -522,30 +522,15 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_SEARCH,
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR),
precondition: 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) { settingsEditorFocusSearch(accessor); }
});
registerAction2(class extends Action2 {
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_SEARCH,
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_TOC_ROW_FOCUS),
keybinding: {
primary: KeyCode.Escape,
weight: KeybindingWeight.WorkbenchContrib,
when: null
},
title: nls.localize('settings.focusSearch', "Focus settings search")
category,
f1: true,
title: nls.localize('settings.focusSearch', "Focus Settings Search")
});
}
@ -556,13 +541,15 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS,
precondition: CONTEXT_SETTINGS_SEARCH_FOCUS,
precondition: CONTEXT_SETTINGS_EDITOR,
keybinding: {
primary: KeyCode.Escape,
weight: KeybindingWeight.EditorContrib,
when: null
when: CONTEXT_SETTINGS_SEARCH_FOCUS
},
title: nls.localize('settings.clearResults', "Clear settings search results")
category,
f1: true,
title: nls.localize('settings.clearResults', "Clear Settings Search Results")
});
}
@ -714,18 +701,16 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_FOCUS_TOC,
precondition: CONTEXT_SETTINGS_EDITOR,
f1: true,
keybinding: [
{
primary: KeyCode.Escape,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_TOC_ROW_FOCUS.negate()),
},
{
primary: KeyCode.LeftArrow,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_TOC_ROW_FOCUS.negate(), InputFocusedContext.negate())
when: CONTEXT_SETTINGS_ROW_FOCUS
}],
title: nls.localize('settings.focusSettingsTOC', "Focus settings TOC tree")
category,
title: nls.localize('settings.focusSettingsTOC', "Focus Settings Table of Contents")
});
}
@ -735,11 +720,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
return;
}
if (document.activeElement?.classList.contains('monaco-list')) {
preferencesEditor.focusTOC();
} else {
preferencesEditor.focusSettings();
}
preferencesEditor.focusTOC();
}
});
@ -747,12 +728,12 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_FOCUS_CONTROL,
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_TOC_ROW_FOCUS.negate(), WorkbenchListFocusContextKey),
precondition: CONTEXT_SETTINGS_ROW_FOCUS,
keybinding: {
primary: KeyCode.Enter,
weight: KeybindingWeight.WorkbenchContrib,
},
title: nls.localize('settings.focusSettingControl', "Focus setting control")
title: nls.localize('settings.focusSettingControl', "Focus Setting Control")
});
}
@ -771,9 +752,40 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
registerAction2(class extends Action2 {
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_FOCUS_TOC2,
id: SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU,
precondition: CONTEXT_SETTINGS_EDITOR,
keybinding: {
primary: KeyMod.Shift | KeyCode.F9,
weight: KeybindingWeight.WorkbenchContrib,
when: null
},
f1: true,
category,
title: nls.localize('settings.showContextMenu', "Show Setting Context Menu")
});
}
title: nls.localize('settings.focusSettingsTOC', "Focus settings TOC tree")
run(accessor: ServicesAccessor): void {
const preferencesEditor = getPreferencesEditor(accessor);
if (preferencesEditor instanceof SettingsEditor2) {
preferencesEditor.showContextMenu();
}
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_FOCUS_UP,
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS.toNegated()),
keybinding: {
primary: KeyCode.Escape,
weight: KeybindingWeight.WorkbenchContrib,
when: null
},
f1: true,
category,
title: nls.localize('settings.focusLevelUp', "Move Focus Up One Level")
});
}
@ -783,28 +795,12 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
return;
}
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();
if (preferencesEditor.currentFocusContext === SettingsFocusContext.SettingControl) {
preferencesEditor.focusSettings();
} else if (preferencesEditor.currentFocusContext === SettingsFocusContext.SettingTree) {
preferencesEditor.focusTOC();
} else if (preferencesEditor.currentFocusContext === SettingsFocusContext.TableOfContents) {
preferencesEditor.focusSearch();
}
}
});

View file

@ -46,13 +46,20 @@ import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickE
import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels';
import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browser/settingsWidgets';
import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree';
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS } from 'vs/workbench/contrib/preferences/common/preferences';
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS } from 'vs/workbench/contrib/preferences/common/preferences';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
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 { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync';
export const enum SettingsFocusContext {
Search,
TableOfContents,
SettingTree,
SettingControl
}
function createGroupIterator(group: SettingsTreeGroupElement): Iterable<ITreeElement<SettingsTreeGroupChild>> {
return Iterable.map(group.children, g => {
return {
@ -134,11 +141,12 @@ export class SettingsEditor2 extends EditorPane {
private _searchResultModel: SearchResultModel | null = null;
private tocRowFocused: IContextKey<boolean>;
private settingRowFocused: IContextKey<boolean>;
private inSettingsEditorContextKey: IContextKey<boolean>;
private searchFocusContextKey: IContextKey<boolean>;
private scheduledRefreshes: Map<string, DOM.IFocusTracker>;
private lastFocusedSettingElement: string | null = null;
private _currentFocusContext: SettingsFocusContext = SettingsFocusContext.Search;
/** Don't spam warnings */
private hasWarnedMissingSettings = false;
@ -180,6 +188,7 @@ export class SettingsEditor2 extends EditorPane {
this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService);
this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService);
this.tocRowFocused = CONTEXT_TOC_ROW_FOCUS.bindTo(contextKeyService);
this.settingRowFocused = CONTEXT_SETTINGS_ROW_FOCUS.bindTo(contextKeyService);
this.scheduledRefreshes = new Map<string, DOM.IFocusTracker>();
@ -215,6 +224,19 @@ export class SettingsEditor2 extends EditorPane {
this.rootElement.classList.toggle('search-mode', !!this._searchResultModel);
}
private get focusedSettingDOMElement(): HTMLElement | undefined {
const focused = this.settingsTree.getFocus()[0];
if (!(focused instanceof SettingsTreeSettingElement)) {
return;
}
return this.settingRenderers.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), focused.setting.key)[0];
}
get currentFocusContext() {
return this._currentFocusContext;
}
createEditor(parent: HTMLElement): void {
parent.setAttribute('tabindex', '-1');
this.rootElement = DOM.append(parent, $('.settings-editor', { tabindex: '-1' }));
@ -311,23 +333,27 @@ export class SettingsEditor2 extends EditorPane {
const monacoWidth = innerWidth - 10 - this.countElement.clientWidth - this.controlsElement.clientWidth - 12;
this.searchWidget.layout({ height: 20, width: monacoWidth });
DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600);
DOM.toggleClass(this.rootElement, 'narrow-width', dimension.width < 600);
this.rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600);
this.rootElement.classList.toggle('narrow-width', dimension.width < 600);
}
focus(): void {
if (this.lastFocusedSettingElement) {
const elements = this.settingRenderers.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), this.lastFocusedSettingElement);
if (elements.length) {
const control = elements[0].querySelector(AbstractSettingRenderer.CONTROL_SELECTOR);
if (this._currentFocusContext === SettingsFocusContext.Search) {
this.focusSearch();
} else if (this._currentFocusContext === SettingsFocusContext.SettingControl) {
const element = this.focusedSettingDOMElement;
if (element) {
const control = element.querySelector(AbstractSettingRenderer.CONTROL_SELECTOR);
if (control) {
(<HTMLElement>control).focus();
return;
}
}
} else if (this._currentFocusContext === SettingsFocusContext.SettingTree) {
this.settingsTree.domFocus();
} else if (this._currentFocusContext === SettingsFocusContext.TableOfContents) {
this.tocTree.domFocus();
}
this.focusSearch();
}
protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
@ -362,24 +388,10 @@ export class SettingsEditor2 extends EditorPane {
}
showContextMenu(): void {
const activeElement = this.getActiveElementInSettingsTree();
if (!activeElement) {
return;
}
const settingDOMElement = this.settingRenderers.getSettingDOMElementForDOMElement(activeElement);
if (!settingDOMElement) {
return;
}
const focusedKey = this.settingRenderers.getKeyForDOMElementInSetting(settingDOMElement);
if (!focusedKey) {
return;
}
const elements = this.currentSettingsModel.getElementsByName(focusedKey);
if (elements && elements[0]) {
this.settingRenderers.showContextMenu(elements[0], settingDOMElement);
const focused = this.settingsTree.getFocus()[0];
const rowElement = this.focusedSettingDOMElement;
if (rowElement && focused instanceof SettingsTreeSettingElement) {
this.settingRenderers.showContextMenu(focused, rowElement);
}
}
@ -429,11 +441,9 @@ export class SettingsEditor2 extends EditorPane {
placeholderText: searchBoxLabel,
focusContextKey: this.searchFocusContextKey,
// TODO: Aria-live
})
);
}));
this._register(this.searchWidget.onFocus(() => {
this.lastFocusedSettingElement = '';
this._currentFocusContext = SettingsFocusContext.Search;
}));
this._register(attachSuggestEnabledInputBoxStyler(this.searchWidget, this.themeService, {
@ -597,6 +607,10 @@ export class SettingsEditor2 extends EditorPane {
})),
this.viewState));
this._register(this.tocTree.onDidFocus(() => {
this._currentFocusContext = SettingsFocusContext.TableOfContents;
}));
this._register(this.tocTree.onDidChangeFocus(e => {
const element: SettingsTreeGroupElement | null = e.elements[0];
if (this.tocFocusedElement === element) {
@ -636,8 +650,9 @@ export class SettingsEditor2 extends EditorPane {
}));
this._register(this.settingRenderers.onDidClickSettingLink(settingName => this.onDidClickSetting(settingName)));
this._register(this.settingRenderers.onDidFocusSetting(element => {
this.lastFocusedSettingElement = element.setting.key;
this.settingsTree.setFocus([element]);
this._currentFocusContext = SettingsFocusContext.SettingControl;
this.settingRowFocused.set(false);
}));
this._register(this.settingRenderers.onDidClickOverrideElement((element: ISettingOverrideClickEvent) => {
if (element.scope.toLowerCase() === 'workspace') {
@ -671,6 +686,15 @@ export class SettingsEditor2 extends EditorPane {
}, 0);
}));
this._register(this.settingsTree.onDidFocus(() => {
this._currentFocusContext = SettingsFocusContext.SettingTree;
this.settingRowFocused.set(true);
}));
this._register(this.settingsTree.onDidBlur(() => {
this.settingRowFocused.set(false);
}));
// There is no different select state in the settings tree
this._register(this.settingsTree.onDidChangeFocus(e => {
const element = e.elements[0];
@ -887,7 +911,7 @@ export class SettingsEditor2 extends EditorPane {
}
private onSearchModeToggled(): void {
this.rootElement.classList.add('no-toc-search');
this.rootElement.classList.remove('no-toc-search');
if (this.configurationService.getValue('workbench.settings.settingsSearchTocBehavior') === 'hide') {
this.rootElement.classList.toggle('no-toc-search', !!this.searchResultModel);
}
@ -984,7 +1008,7 @@ export class SettingsEditor2 extends EditorPane {
}
}
private getActiveElementInSettingsTree(): HTMLElement | null {
private getActiveControlInSettingsTree(): HTMLElement | null {
return (document.activeElement && DOM.isAncestor(document.activeElement, this.settingsTree.getHTMLElement())) ?
<HTMLElement>document.activeElement :
null;
@ -1006,7 +1030,7 @@ export class SettingsEditor2 extends EditorPane {
}
// If a setting control is currently focused, schedule a refresh for later
const activeElement = this.getActiveElementInSettingsTree();
const activeElement = this.getActiveControlInSettingsTree();
const focusedSetting = activeElement && this.settingRenderers.getSettingDOMElementForDOMElement(activeElement);
if (focusedSetting && !force) {
// If a single setting is being refreshed, it's ok to refresh now if that is not the focused setting
@ -1067,7 +1091,7 @@ export class SettingsEditor2 extends EditorPane {
const isModified = dataElements && dataElements[0] && dataElements[0].isConfigured; // all elements are either configured or not
const elements = this.settingRenderers.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), key);
if (elements && elements[0]) {
DOM.toggleClass(elements[0], 'is-configured', !!isModified);
elements[0].classList.toggle('is-configured', !!isModified);
}
}
@ -1282,7 +1306,7 @@ export class SettingsEditor2 extends EditorPane {
this.layout(this.dimension);
}
DOM.removeClass(this.rootElement, 'no-results');
this.rootElement.classList.remove('no-results');
return;
}
@ -1298,7 +1322,7 @@ export class SettingsEditor2 extends EditorPane {
this.countElement.style.display = 'block';
this.layout(this.dimension);
}
DOM.toggleClass(this.rootElement, 'no-results', count === 0);
this.rootElement.classList.toggle('no-results', count === 0);
}
}

View file

@ -869,6 +869,7 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I
common.toDispose.add(openSettingsButton.onDidClick(() => template.onChange!()));
openSettingsButton.label = SettingComplexRenderer.EDIT_IN_JSON_LABEL;
openSettingsButton.element.classList.add('edit-in-settings-button');
openSettingsButton.element.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
common.toDispose.add(attachButtonStyler(openSettingsButton, this._themeService, {
buttonBackground: Color.transparent.toString(),

View file

@ -73,6 +73,7 @@ export const CONTEXT_SETTINGS_EDITOR = new RawContextKey<boolean>('inSettingsEdi
export const CONTEXT_SETTINGS_JSON_EDITOR = new RawContextKey<boolean>('inSettingsJSONEditor', false);
export const CONTEXT_SETTINGS_SEARCH_FOCUS = new RawContextKey<boolean>('inSettingsSearch', false);
export const CONTEXT_TOC_ROW_FOCUS = new RawContextKey<boolean>('settingsTocRowFocus', false);
export const CONTEXT_SETTINGS_ROW_FOCUS = new RawContextKey<boolean>('settingRowFocus', false);
export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey<boolean>('inKeybindings', false);
export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey<boolean>('inKeybindingsSearch', false);
export const CONTEXT_KEYBINDING_FOCUS = new RawContextKey<boolean>('keybindingFocus', false);

View file

@ -1311,7 +1311,7 @@ class SCMInputWidget extends Disposable {
if (value === textModel.getValue()) { // circuit breaker
return;
}
textModel.setValue(value);
textModel.setValue(input.value);
this.inputEditor.setPosition(textModel.getFullModelRange().getEndPosition());
}));
@ -1381,7 +1381,7 @@ class SCMInputWidget extends Disposable {
@IKeybindingService private keybindingService: IKeybindingService,
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IContextViewService private readonly contextViewService: IContextViewService
) {
super();

View file

@ -8,12 +8,20 @@ import { Event, Emitter } from 'vs/base/common/event';
import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator } from './scm';
import { ILogService } from 'vs/platform/log/common/log';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
class SCMInput implements ISCMInput {
private _value = '';
get value(): string {
if (this.root) {
const key = `scm/input:${this.repository.provider.label}:${this.root.path}`;
let storedValue = this.storageService.get(key, StorageScope.WORKSPACE);
if (storedValue) {
return storedValue;
}
}
return this._value;
}
@ -21,11 +29,14 @@ class SCMInput implements ISCMInput {
if (value === this._value) {
return;
}
this._value = value;
if (this.root) {
const key = `scm/input:${this.repository.provider.label}:${this.root.path}`;
this.storageService.store(key, value, StorageScope.WORKSPACE);
}
this._onDidChange.fire(value);
}
private root;
private readonly _onDidChange = new Emitter<string>();
readonly onDidChange: Event<string> = this._onDidChange.event;
@ -55,7 +66,8 @@ class SCMInput implements ISCMInput {
}
private readonly _onDidChangeVisibility = new Emitter<boolean>();
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility
.event;
private _validateInput: IInputValidator = () => Promise.resolve(undefined);
@ -71,7 +83,13 @@ class SCMInput implements ISCMInput {
private readonly _onDidChangeValidateInput = new Emitter<void>();
readonly onDidChangeValidateInput: Event<void> = this._onDidChangeValidateInput.event;
constructor(readonly repository: ISCMRepository) { }
constructor(
readonly repository: ISCMRepository,
@IStorageService private storageService: IStorageService
) {
this.root = this.repository.provider.rootUri;
this._value = this.value;
}
}
class SCMRepository implements ISCMRepository {
@ -84,11 +102,12 @@ class SCMRepository implements ISCMRepository {
private readonly _onDidChangeSelection = new Emitter<boolean>();
readonly onDidChangeSelection: Event<boolean> = this._onDidChangeSelection.event;
readonly input: ISCMInput = new SCMInput(this);
readonly input: ISCMInput = new SCMInput(this, this.storageService);
constructor(
public readonly provider: ISCMProvider,
private disposable: IDisposable
private disposable: IDisposable,
@IStorageService private storageService: IStorageService
) { }
setSelected(selected: boolean): void {
@ -128,7 +147,8 @@ export class SCMService implements ISCMService {
constructor(
@ILogService private readonly logService: ILogService,
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IStorageService private storageService: IStorageService
) {
this.providerCount = contextKeyService.createKey('scm.providerCount', 0);
}
@ -161,7 +181,7 @@ export class SCMService implements ISCMService {
this.providerCount.set(this._repositories.length);
});
const repository = new SCMRepository(provider, disposable);
const repository = new SCMRepository(provider, disposable, this.storageService);
const selectedDisposable = Event.map(Event.filter(repository.onDidChangeSelection, selected => selected), _ => repository)(this.select, this);
this._repositories.push(repository);

View file

@ -443,6 +443,7 @@ export class SimpleRemoteAgentService implements IRemoteAgentService {
async flushTelemetry(): Promise<void> { }
async getRawEnvironment(): Promise<IRemoteAgentEnvironment | null> { return null; }
async scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise<IExtensionDescription[]> { return []; }
async scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise<IExtensionDescription | null> { return null; }
}
//#endregion

View file

@ -13,7 +13,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { toResource, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor';
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/windows/common/windows';
import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/windows/common/windows';
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { applyZoom } from 'vs/platform/windows/electron-sandbox/window';
@ -54,7 +54,7 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { posix, dirname } from 'vs/base/common/path';
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 { IWorkbenchLayoutService, Parts, positionFromString, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
@ -266,6 +266,12 @@ export class NativeWindow extends Disposable {
)(e => this.onDidChangeMaximized(e)));
this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false);
// Detect panel position to determine minimum width
this._register(this.layoutService.onPanelPositionChange(pos => {
this.onDidPanelPositionChange(positionFromString(pos));
}));
this.onDidPanelPositionChange(this.layoutService.getPanelPosition());
}
private updateDocumentEdited(isDirty = this.workingCopyService.hasDirty): void {
@ -280,6 +286,22 @@ export class NativeWindow extends Disposable {
this.layoutService.updateWindowMaximizedState(maximized);
}
private getWindowMinimumWidth(panelPosition: Position = this.layoutService.getPanelPosition()): number {
// if panel is on the side, then return the larger minwidth
const panelOnSide = panelPosition === Position.LEFT || panelPosition === Position.RIGHT;
if (panelOnSide) {
return WindowMinimumSize.WIDTH_WITH_VERTICAL_PANEL;
}
else {
return WindowMinimumSize.WIDTH;
}
}
private onDidPanelPositionChange(pos: Position): void {
const minWidth = this.getWindowMinimumWidth(pos);
this.nativeHostService.setMinimumSize(minWidth, undefined);
}
private onDidVisibleEditorsChange(): void {
// Close when empty: check if we should close the window based on the setting

View file

@ -7,6 +7,7 @@ import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtension, IScannedExtension, ExtensionType, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
export const IExtensionManagementServerService = createDecorator<IExtensionManagementServerService>('extensionManagementServerService');
@ -90,6 +91,7 @@ export interface IWebExtensionsScannerService {
readonly _serviceBrand: undefined;
scanExtensions(type?: ExtensionType): Promise<IScannedExtension[]>;
scanAndTranslateExtensions(type?: ExtensionType): Promise<ITranslatedScannedExtension[]>;
scanAndTranslateSingleExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<ITranslatedScannedExtension | null>;
canAddExtension(galleryExtension: IGalleryExtension): Promise<boolean>;
addExtension(galleryExtension: IGalleryExtension): Promise<IScannedExtension>;
removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void>;

View file

@ -170,6 +170,34 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return Promise.all(extensions.map((ext) => this._translateScannedExtension(ext)));
}
async scanAndTranslateSingleExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<ITranslatedScannedExtension | null> {
const extension = await this._scanSingleExtension(extensionLocation, extensionType);
if (extension) {
return this._translateScannedExtension(extension);
}
return null;
}
private async _scanSingleExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IScannedExtension | null> {
if (extensionType === ExtensionType.System) {
const systemExtensions = await this.systemExtensionsPromise;
return this._findScannedExtension(systemExtensions, extensionLocation);
}
const staticExtensions = await this.defaultExtensionsPromise;
const userExtensions = await this.scanUserExtensions();
return this._findScannedExtension(staticExtensions.concat(userExtensions), extensionLocation);
}
private _findScannedExtension(candidates: IScannedExtension[], extensionLocation: URI): IScannedExtension | null {
for (const candidate of candidates) {
if (candidate.location.toString() === extensionLocation.toString()) {
return candidate;
}
}
return null;
}
private async _translateScannedExtension(scannedExtension: IScannedExtension): Promise<ITranslatedScannedExtension> {
let manifest = scannedExtension.packageJSON;
if (scannedExtension.packageNLS) {

View file

@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { distinct } from 'vs/base/common/arrays';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { IExtensionIgnoredRecommendationsService, IgnoredRecommendationChangeNotification } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
const ignoredRecommendationsStorageKey = 'extensionsAssistant/ignored_recommendations';
export class ExtensionIgnoredRecommendationsService extends Disposable implements IExtensionIgnoredRecommendationsService {
declare readonly _serviceBrand: undefined;
private _onDidChangeIgnoredRecommendations = this._register(new Emitter<void>());
readonly onDidChangeIgnoredRecommendations = this._onDidChangeIgnoredRecommendations.event;
// Global Ignored Recommendations
private _globalIgnoredRecommendations: string[] = [];
get globalIgnoredRecommendations(): string[] { return [...this._globalIgnoredRecommendations]; }
private _onDidChangeGlobalIgnoredRecommendation = this._register(new Emitter<IgnoredRecommendationChangeNotification>());
readonly onDidChangeGlobalIgnoredRecommendation = this._onDidChangeGlobalIgnoredRecommendation.event;
// Ignored Workspace Recommendations
private ignoredWorkspaceRecommendations: string[] = [];
get ignoredRecommendations(): string[] { return distinct([...this.globalIgnoredRecommendations, ...this.ignoredWorkspaceRecommendations]); }
constructor(
@IWorkpsaceExtensionsConfigService private readonly workpsaceExtensionsConfigService: IWorkpsaceExtensionsConfigService,
@IStorageService private readonly storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super();
storageKeysSyncRegistryService.registerStorageKey({ key: ignoredRecommendationsStorageKey, version: 1 });
this._globalIgnoredRecommendations = this.getCachedIgnoredRecommendations();
this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
this.initIgnoredWorkspaceRecommendations();
}
private async initIgnoredWorkspaceRecommendations(): Promise<void> {
this.ignoredWorkspaceRecommendations = await this.workpsaceExtensionsConfigService.getUnwantedRecommendations();
this._onDidChangeIgnoredRecommendations.fire();
this._register(this.workpsaceExtensionsConfigService.onDidChangeExtensionsConfigs(async () => {
this.ignoredWorkspaceRecommendations = await this.workpsaceExtensionsConfigService.getUnwantedRecommendations();
this._onDidChangeIgnoredRecommendations.fire();
}));
}
toggleGlobalIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void {
extensionId = extensionId.toLowerCase();
const ignored = this._globalIgnoredRecommendations.indexOf(extensionId) !== -1;
if (ignored === shouldIgnore) {
return;
}
this._globalIgnoredRecommendations = shouldIgnore ? [...this._globalIgnoredRecommendations, extensionId] : this._globalIgnoredRecommendations.filter(id => id !== extensionId);
this.storeCachedIgnoredRecommendations(this._globalIgnoredRecommendations);
this._onDidChangeGlobalIgnoredRecommendation.fire({ extensionId, isRecommended: !shouldIgnore });
this._onDidChangeIgnoredRecommendations.fire();
}
private getCachedIgnoredRecommendations(): string[] {
const ignoredRecommendations: string[] = JSON.parse(this.ignoredRecommendationsValue);
return ignoredRecommendations.map(e => e.toLowerCase());
}
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._globalIgnoredRecommendations = this.getCachedIgnoredRecommendations();
this._onDidChangeIgnoredRecommendations.fire();
}
}
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);
}
}
registerSingleton(IExtensionIgnoredRecommendationsService, ExtensionIgnoredRecommendationsService);

View file

@ -3,20 +3,15 @@
* 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 { IStringDictionary } from 'vs/base/common/collections';
import { Event } from 'vs/base/common/event';
export interface IExtensionsConfigContent {
recommendations: string[];
unwantedRecommendations: string[];
}
export type RecommendationChangeNotification = {
extensionId: string,
isRecommended: boolean
};
export type DynamicRecommendation = 'dynamic';
export type ConfigRecommendation = 'config';
export type ExecutableRecommendation = 'executable';
@ -44,7 +39,9 @@ export const IExtensionRecommendationsService = createDecorator<IExtensionRecomm
export interface IExtensionRecommendationsService {
readonly _serviceBrand: undefined;
readonly onDidChangeRecommendations: Event<void>;
getAllRecommendationsWithReason(): IStringDictionary<IExtensionRecommendationReson>;
getImportantRecommendations(): Promise<string[]>;
getOtherRecommendations(): Promise<string[]>;
getFileBasedRecommendations(): string[];
@ -52,8 +49,24 @@ export interface IExtensionRecommendationsService {
getConfigBasedRecommendations(): Promise<{ important: string[], others: string[] }>;
getWorkspaceRecommendations(): Promise<string[]>;
getKeymapRecommendations(): string[];
toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void;
getIgnoredRecommendations(): ReadonlyArray<string>;
onRecommendationChange: Event<RecommendationChangeNotification>;
}
export type IgnoredRecommendationChangeNotification = {
extensionId: string,
isRecommended: boolean
};
export const IExtensionIgnoredRecommendationsService = createDecorator<IExtensionIgnoredRecommendationsService>('IExtensionIgnoredRecommendationsService');
export interface IExtensionIgnoredRecommendationsService {
readonly _serviceBrand: undefined;
onDidChangeIgnoredRecommendations: Event<void>;
readonly ignoredRecommendations: string[];
onDidChangeGlobalIgnoredRecommendation: Event<IgnoredRecommendationChangeNotification>;
readonly globalIgnoredRecommendations: string[];
toggleGlobalIgnoredRecommendation(extensionId: string, ignore: boolean): void;
}

View file

@ -4,7 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { coalesce, distinct, flatten } from 'vs/base/common/arrays';
import { Emitter, Event } from 'vs/base/common/event';
import { parse } from 'vs/base/common/json';
import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService } from 'vs/platform/files/common/files';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@ -22,19 +24,26 @@ export const IWorkpsaceExtensionsConfigService = createDecorator<IWorkpsaceExten
export interface IWorkpsaceExtensionsConfigService {
readonly _serviceBrand: undefined;
onDidChangeExtensionsConfigs: Event<void>;
getExtensionsConfigs(): Promise<IExtensionsConfigContent[]>;
getUnwantedRecommendations(): Promise<string[]>;
}
export class WorkspaceExtensionsConfigService implements IWorkpsaceExtensionsConfigService {
export class WorkspaceExtensionsConfigService extends Disposable implements IWorkpsaceExtensionsConfigService {
declare readonly _serviceBrand: undefined;
private readonly _onDidChangeExtensionsConfigs = this._register(new Emitter<void>());
readonly onDidChangeExtensionsConfigs = this._onDidChangeExtensionsConfigs.event;
constructor(
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IFileService private readonly fileService: IFileService,
) { }
) {
super();
this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(e => this._onDidChangeExtensionsConfigs.fire()));
}
async getExtensionsConfigs(): Promise<IExtensionsConfigContent[]> {
const workspace = this.workspaceContextService.getWorkspace();
@ -47,7 +56,7 @@ export class WorkspaceExtensionsConfigService implements IWorkpsaceExtensionsCon
async getUnwantedRecommendations(): Promise<string[]> {
const configs = await this.getExtensionsConfigs();
return distinct(flatten(configs.map(c => c.unwantedRecommendations)));
return distinct(flatten(configs.map(c => c.unwantedRecommendations.map(c => c.toLowerCase()))));
}
private async resolveWorkspaceExtensionConfig(workspace: IWorkspace): Promise<IExtensionsConfigContent | null> {

View file

@ -7,8 +7,7 @@ import { URI } from 'vs/base/common/uri';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
import * as dom from 'vs/base/browser/dom';
import { Schemas } from 'vs/base/common/network';
import { FileAccess, Schemas } from 'vs/base/common/network';
class ExtensionResourceLoaderService implements IExtensionResourceLoaderService {
@ -19,7 +18,7 @@ class ExtensionResourceLoaderService implements IExtensionResourceLoaderService
) { }
async readExtensionResource(uri: URI): Promise<string> {
uri = dom.asDomUri(uri);
uri = FileAccess.asBrowserUri(uri);
if (uri.scheme !== Schemas.http && uri.scheme !== Schemas.https) {
const result = await this._fileService.readFile(uri);

View file

@ -9,7 +9,7 @@ import { IWorkbenchExtensionEnablementService, IWebExtensionsScannerService } fr
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionService, IExtensionHost } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionService, IExtensionHost, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IProductService } from 'vs/platform/product/common/productService';
@ -19,13 +19,15 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionIdentifier, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription, ExtensionKind, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
@ -41,6 +43,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,
@IFileService fileService: IFileService,
@IProductService productService: IProductService,
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IConfigurationService private readonly _configService: IConfigurationService,
@ -56,6 +60,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
extensionEnablementService,
fileService,
productService,
extensionManagementService,
contextService,
);
this._runningLocation = new Map<string, ExtensionRunningLocation>();
@ -74,6 +80,71 @@ export class ExtensionService extends AbstractExtensionService implements IExten
super.dispose();
}
protected _canAddExtension(extension: IExtension): boolean {
const extensionKind = getExtensionKind(extension.manifest, this._productService, this._configService);
const isRemote = extension.location.scheme === Schemas.vscodeRemote;
const runningLocation = pickRunningLocation(extensionKind, !isRemote, isRemote);
if (runningLocation === ExtensionRunningLocation.None) {
return false;
}
return super._canAddExtension(extension);
}
protected async _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null> {
if (extension.location.scheme === Schemas.vscodeRemote) {
return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System);
}
const scannedExtension = await this._webExtensionsScannerService.scanAndTranslateSingleExtension(extension.location, extension.type);
if (scannedExtension) {
return parseScannedExtension(scannedExtension);
}
return null;
}
protected async _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
let localToAdd: IExtensionDescription[] = [];
let remoteToAdd: IExtensionDescription[] = [];
for (const extension of toAdd) {
const extensionKind = getExtensionKind(extension, this._productService, this._configService);
const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote;
const runningLocation = pickRunningLocation(extensionKind, !isRemote, isRemote);
this._runningLocation.set(ExtensionIdentifier.toKey(extension.identifier), runningLocation);
if (runningLocation === ExtensionRunningLocation.LocalWebWorker) {
localToAdd.push(extension);
} else if (runningLocation === ExtensionRunningLocation.Remote) {
remoteToAdd.push(extension);
}
}
let localToRemove: ExtensionIdentifier[] = [];
let remoteToRemove: ExtensionIdentifier[] = [];
for (const extensionId of toRemove) {
const runningLocation = this._runningLocation.get(ExtensionIdentifier.toKey(extensionId));
this._runningLocation.delete(ExtensionIdentifier.toKey(extensionId));
if (runningLocation === ExtensionRunningLocation.LocalWebWorker) {
localToRemove.push(extensionId);
} else if (runningLocation === ExtensionRunningLocation.Remote) {
remoteToRemove.push(extensionId);
}
}
if (localToAdd.length > 0 || localToRemove.length > 0) {
const localWebWorkerExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalWebWorker);
if (localWebWorkerExtensionHost) {
await localWebWorkerExtensionHost.deltaExtensions(localToAdd, localToRemove);
}
}
if (remoteToAdd.length > 0 || remoteToRemove.length > 0) {
const remoteExtensionHost = this._getExtensionHostManager(ExtensionHostKind.Remote);
if (remoteExtensionHost) {
await remoteExtensionHost.deltaExtensions(remoteToAdd, remoteToRemove);
}
}
}
private _initFetchFileSystem(): void {
const provider = new FetchFileSystemProvider();
this._disposables.add(this._fileService.registerProvider(Schemas.http, provider));
@ -170,37 +241,41 @@ const enum ExtensionRunningLocation {
Remote
}
export function determineRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], allExtensionKinds: Map<string, ExtensionKind[]>, hasRemote: boolean): Map<string, ExtensionRunningLocation> {
function pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean): ExtensionRunningLocation {
for (const extensionKind of extensionKinds) {
if (extensionKind === 'ui' && isInstalledRemotely) {
// ui extensions run remotely if possible
return ExtensionRunningLocation.Remote;
}
if (extensionKind === 'workspace' && isInstalledRemotely) {
// workspace extensions run remotely if possible
return ExtensionRunningLocation.Remote;
}
if (extensionKind === 'web' && isInstalledLocally) {
// web worker extensions run in the local web worker if possible
return ExtensionRunningLocation.LocalWebWorker;
}
}
return ExtensionRunningLocation.None;
}
function determineRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], allExtensionKinds: Map<string, ExtensionKind[]>, hasRemote: boolean): Map<string, ExtensionRunningLocation> {
const localExtensionsSet = new Set<string>();
localExtensions.forEach(ext => localExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
const remoteExtensionsSet = new Set<string>();
remoteExtensions.forEach(ext => remoteExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
const pickRunningLocation = (extension: IExtensionDescription): ExtensionRunningLocation => {
const _pickRunningLocation = (extension: IExtensionDescription): ExtensionRunningLocation => {
const isInstalledLocally = localExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
const isInstalledRemotely = remoteExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
const extensionKinds = allExtensionKinds.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
for (const extensionKind of extensionKinds) {
if (extensionKind === 'ui' && isInstalledRemotely) {
// ui extensions run remotely if possible
return ExtensionRunningLocation.Remote;
}
if (extensionKind === 'workspace' && isInstalledRemotely) {
// workspace extensions run remotely if possible
return ExtensionRunningLocation.Remote;
}
if (extensionKind === 'web' && isInstalledLocally) {
// web worker extensions run in the local web worker if possible
return ExtensionRunningLocation.LocalWebWorker;
}
}
return ExtensionRunningLocation.None;
return pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely);
};
const runningLocation = new Map<string, ExtensionRunningLocation>();
localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext)));
remoteExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext)));
localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), _pickRunningLocation(ext)));
remoteExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), _pickRunningLocation(ext)));
return runningLocation;
}

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { Barrier } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
@ -15,16 +16,19 @@ import { BetterMergeId } from 'vs/platform/extensionManagement/common/extensionM
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind } from 'vs/workbench/services/extensions/common/extensions';
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension, IExtension } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IProductService } from 'vs/platform/product/common/productService';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionActivationHost as IWorkspaceContainsActivationHost, checkGlobFileExists, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
@ -39,6 +43,13 @@ export function parseScannedExtension(extension: ITranslatedScannedExtension): I
};
}
class DeltaExtensionsQueueItem {
constructor(
public readonly toAdd: IExtension[],
public readonly toRemove: string[]
) { }
}
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: undefined;
@ -66,6 +77,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
private readonly _proposedApiController: ProposedApiController;
private readonly _isExtensionDevHost: boolean;
protected readonly _isExtensionDevTestFromCli: boolean;
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
private _inHandleDeltaExtensions: boolean;
// --- Members used per extension host process
protected _extensionHostManagers: ExtensionHostManager[];
@ -80,7 +93,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
@ITelemetryService protected readonly _telemetryService: ITelemetryService,
@IWorkbenchExtensionEnablementService protected readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
@IFileService protected readonly _fileService: IFileService,
@IProductService protected readonly _productService: IProductService
@IProductService protected readonly _productService: IProductService,
@IExtensionManagementService protected readonly _extensionManagementService: IExtensionManagementService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
) {
super();
@ -103,8 +118,230 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
this._deltaExtensionsQueue = [];
this._inHandleDeltaExtensions = false;
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
let toAdd: IExtension[] = [];
let toRemove: string[] = [];
for (const extension of extensions) {
if (this._extensionEnablementService.isEnabled(extension)) {
// an extension has been enabled
toAdd.push(extension);
} else {
// an extension has been disabled
toRemove.push(extension.identifier.id);
}
}
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
}));
this._register(this._extensionManagementService.onDidInstallExtension((event) => {
if (event.local) {
if (this._extensionEnablementService.isEnabled(event.local)) {
// an extension has been installed
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([event.local], []));
}
}
}));
this._register(this._extensionManagementService.onDidUninstallExtension((event) => {
if (!event.error) {
// an extension has been uninstalled
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
}
}));
}
protected _getExtensionHostManager(kind: ExtensionHostKind): ExtensionHostManager | null {
for (const extensionHostManager of this._extensionHostManagers) {
if (extensionHostManager.kind === kind) {
return extensionHostManager;
}
}
return null;
}
//#region deltaExtensions
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
this._deltaExtensionsQueue.push(item);
if (this._inHandleDeltaExtensions) {
// Let the current item finish, the new one will be picked up
return;
}
while (this._deltaExtensionsQueue.length > 0) {
const item = this._deltaExtensionsQueue.shift()!;
try {
this._inHandleDeltaExtensions = true;
await this._deltaExtensions(item.toAdd, item.toRemove);
} finally {
this._inHandleDeltaExtensions = false;
}
}
}
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
let toAdd: IExtensionDescription[] = [];
for (let i = 0, len = _toAdd.length; i < len; i++) {
const extension = _toAdd[i];
if (!this._canAddExtension(extension)) {
continue;
}
const extensionDescription = await this._scanSingleExtension(extension);
if (!extensionDescription) {
// could not scan extension...
continue;
}
toAdd.push(extensionDescription);
}
let toRemove: IExtensionDescription[] = [];
for (let i = 0, len = _toRemove.length; i < len; i++) {
const extensionId = _toRemove[i];
const extensionDescription = this._registry.getExtensionDescription(extensionId);
if (!extensionDescription) {
// ignore disabling/uninstalling an extension which is not running
continue;
}
if (!this.canRemoveExtension(extensionDescription)) {
// uses non-dynamic extension point or is activated
continue;
}
toRemove.push(extensionDescription);
}
if (toAdd.length === 0 && toRemove.length === 0) {
return;
}
// Update the local registry
const result = this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
this._onDidChangeExtensions.fire(undefined);
toRemove = toRemove.concat(result.removedDueToLooping);
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
// enable or disable proposed API per extension
this._checkEnableProposedApi(toAdd);
// Update extension points
this._doHandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
// Update the extension host
await this._updateExtensionsOnExtHosts(toAdd, toRemove.map(e => e.identifier));
for (let i = 0; i < toAdd.length; i++) {
this._activateAddedExtensionIfNeeded(toAdd[i]);
}
}
public canAddExtension(extensionDescription: IExtensionDescription): boolean {
return this._canAddExtension(toExtension(extensionDescription));
}
protected _canAddExtension(extension: IExtension): boolean {
const extensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
if (extensionDescription) {
// this extension is already running (most likely at a different version)
return false;
}
// Check if extension is renamed
if (extension.identifier.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.identifier.uuid)) {
return false;
}
return true;
}
public canRemoveExtension(extension: IExtensionDescription): boolean {
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
if (!extensionDescription) {
// ignore removing an extension which is not running
return false;
}
if (this._extensionHostActiveExtensions.has(ExtensionIdentifier.toKey(extensionDescription.identifier))) {
// Extension is running, cannot remove it safely
return false;
}
return true;
}
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
let shouldActivate = false;
let shouldActivateReason: string | null = null;
let hasWorkspaceContains = false;
if (Array.isArray(extensionDescription.activationEvents)) {
for (let activationEvent of extensionDescription.activationEvents) {
// TODO@joao: there's no easy way to contribute this
if (activationEvent === 'onUri') {
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
}
if (this._allRequestedActivateEvents.has(activationEvent)) {
// This activation event was fired before the extension was added
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
if (activationEvent === '*') {
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
if (/^workspaceContains/.test(activationEvent)) {
hasWorkspaceContains = true;
}
if (activationEvent === 'onStartupFinished') {
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
}
}
if (shouldActivate) {
await Promise.all(
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! }))
).then(() => { });
} else if (hasWorkspaceContains) {
const workspace = await this._contextService.getCompleteWorkspace();
const forceUsingSearch = !!this._environmentService.configuration.remoteAuthority;
const host: IWorkspaceContainsActivationHost = {
folders: workspace.folders.map(folder => folder.uri),
forceUsingSearch: forceUsingSearch,
exists: (uri) => this._fileService.exists(uri),
checkExists: (folders, includes, token) => this._instantiationService.invokeFunction((accessor) => checkGlobFileExists(accessor, folders, includes, token))
};
const result = await checkActivateWorkspaceContainsExtension(host, extensionDescription);
if (!result) {
return;
}
await Promise.all(
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: result.activationEvent }))
).then(() => { });
}
}
//#endregion
protected async _initialize(): Promise<void> {
perf.mark('willLoadExtensions');
this._startExtensionHosts(true, []);
@ -169,14 +406,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
//#region IExtensionService
public canAddExtension(extension: IExtensionDescription): boolean {
return false;
}
public canRemoveExtension(extension: IExtensionDescription): boolean {
return false;
}
public restartExtensionHost(): void {
this._stopExtensionHosts();
this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys()));
@ -463,6 +692,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
protected abstract _createExtensionHosts(isInitialStart: boolean): IExtensionHost[];
protected abstract _scanAndHandleExtensions(): Promise<void>;
protected abstract _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null>;
protected abstract _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void>;
public abstract _onExtensionHostExit(code: number): void;
}

View file

@ -25,7 +25,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExtensionService, toExtension, ExtensionHostKind, IExtensionHost, webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { IProductService } from 'vs/platform/product/common/productService';
@ -37,18 +36,10 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
import { IExtensionActivationHost as IWorkspaceContainsActivationHost, checkGlobFileExists, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { exists } from 'vs/base/node/pfs';
import { ILogService } from 'vs/platform/log/common/log';
import { CATEGORIES } from 'vs/workbench/common/actions';
class DeltaExtensionsQueueItem {
constructor(
public readonly toAdd: IExtension[],
public readonly toRemove: string[]
) { }
}
import { Schemas } from 'vs/base/common/network';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
@ -56,7 +47,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
private readonly _remoteInitData: Map<string, IRemoteExtensionHostInitData>;
private _runningLocation: Map<string, ExtensionRunningLocation>;
private readonly _extensionScanner: CachedExtensionScanner;
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@ -66,7 +56,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,
@IFileService fileService: IFileService,
@IProductService productService: IProductService,
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ -76,7 +67,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IHostService private readonly _hostService: IHostService,
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
@IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@ILogService private readonly _logService: ILogService,
) {
super(
@ -86,7 +76,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
telemetryService,
extensionEnablementService,
fileService,
productService
productService,
extensionManagementService,
contextService,
);
this._enableLocalWebWorker = this._configurationService.getValue<boolean>(webWorkerExtHostConfig);
@ -95,38 +87,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._runningLocation = new Map<string, ExtensionRunningLocation>();
this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner);
this._deltaExtensionsQueue = [];
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
let toAdd: IExtension[] = [];
let toRemove: string[] = [];
for (const extension of extensions) {
if (this._extensionEnablementService.isEnabled(extension)) {
// an extension has been enabled
toAdd.push(extension);
} else {
// an extension has been disabled
toRemove.push(extension.identifier.id);
}
}
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
}));
this._register(this._extensionManagementService.onDidInstallExtension((event) => {
if (event.local) {
if (this._extensionEnablementService.isEnabled(event.local)) {
// an extension has been installed
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([event.local], []));
}
}
}));
this._register(this._extensionManagementService.onDidUninstallExtension((event) => {
if (!event.error) {
// an extension has been uninstalled
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
}
}));
// delay extension host creation and extension scanning
// until the workbench is running. we cannot defer the
@ -154,110 +114,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
}
private _getExtensionHostManager(kind: ExtensionHostKind): ExtensionHostManager | null {
for (const extensionHostManager of this._extensionHostManagers) {
if (extensionHostManager.kind === kind) {
return extensionHostManager;
}
}
return null;
}
//#region deltaExtensions
private _inHandleDeltaExtensions = false;
private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise<void> {
this._deltaExtensionsQueue.push(item);
if (this._inHandleDeltaExtensions) {
// Let the current item finish, the new one will be picked up
return;
}
while (this._deltaExtensionsQueue.length > 0) {
const item = this._deltaExtensionsQueue.shift()!;
try {
this._inHandleDeltaExtensions = true;
await this._deltaExtensions(item.toAdd, item.toRemove);
} finally {
this._inHandleDeltaExtensions = false;
}
}
}
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
if (this._environmentService.configuration.remoteAuthority) {
return;
}
let toAdd: IExtensionDescription[] = [];
for (let i = 0, len = _toAdd.length; i < len; i++) {
const extension = _toAdd[i];
if (!this._canAddExtension(extension)) {
continue;
}
const extensionDescription = await this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System, this.createLogger());
if (!extensionDescription) {
// could not scan extension...
continue;
}
toAdd.push(extensionDescription);
}
let toRemove: IExtensionDescription[] = [];
for (let i = 0, len = _toRemove.length; i < len; i++) {
const extensionId = _toRemove[i];
const extensionDescription = this._registry.getExtensionDescription(extensionId);
if (!extensionDescription) {
// ignore disabling/uninstalling an extension which is not running
continue;
}
if (!this._canRemoveExtension(extensionDescription)) {
// uses non-dynamic extension point or is activated
continue;
}
toRemove.push(extensionDescription);
}
if (toAdd.length === 0 && toRemove.length === 0) {
return;
}
// Update the local registry
const result = this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
this._onDidChangeExtensions.fire(undefined);
toRemove = toRemove.concat(result.removedDueToLooping);
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
// enable or disable proposed API per extension
this._checkEnableProposedApi(toAdd);
// Update extension points
this._doHandleExtensionPoints((<IExtensionDescription[]>[]).concat(toAdd).concat(toRemove));
// Update the extension host
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess);
if (localProcessExtensionHost) {
await localProcessExtensionHost.deltaExtensions(toAdd, toRemove.map(e => e.identifier));
}
for (let i = 0; i < toAdd.length; i++) {
this._activateAddedExtensionIfNeeded(toAdd[i]);
}
}
public canAddExtension(extensionDescription: IExtensionDescription): boolean {
return this._canAddExtension(toExtension(extensionDescription));
}
private _canAddExtension(extension: IExtension): boolean {
protected _canAddExtension(extension: IExtension): boolean {
if (this._environmentService.configuration.remoteAuthority) {
return false;
}
@ -266,18 +123,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return false;
}
const extensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
if (extensionDescription) {
// this extension is already running (most likely at a different version)
return false;
}
// Check if extension is renamed
if (extension.identifier.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.identifier.uuid)) {
return false;
}
return true;
return super._canAddExtension(extension);
}
public canRemoveExtension(extension: IExtensionDescription): boolean {
@ -289,88 +135,20 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return false;
}
const extensionDescription = this._registry.getExtensionDescription(extension.identifier);
if (!extensionDescription) {
// ignore removing an extension which is not running
return false;
}
return this._canRemoveExtension(extensionDescription);
return super.canRemoveExtension(extension);
}
private _canRemoveExtension(extension: IExtensionDescription): boolean {
if (this._extensionHostActiveExtensions.has(ExtensionIdentifier.toKey(extension.identifier))) {
// Extension is running, cannot remove it safely
return false;
}
return true;
protected _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null> {
return this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System, this.createLogger());
}
private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise<void> {
let shouldActivate = false;
let shouldActivateReason: string | null = null;
let hasWorkspaceContains = false;
if (Array.isArray(extensionDescription.activationEvents)) {
for (let activationEvent of extensionDescription.activationEvents) {
// TODO@joao: there's no easy way to contribute this
if (activationEvent === 'onUri') {
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
}
if (this._allRequestedActivateEvents.has(activationEvent)) {
// This activation event was fired before the extension was added
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
if (activationEvent === '*') {
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
if (/^workspaceContains/.test(activationEvent)) {
hasWorkspaceContains = true;
}
if (activationEvent === 'onStartupFinished') {
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
}
}
}
if (shouldActivate) {
await Promise.all(
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! }))
).then(() => { });
} else if (hasWorkspaceContains) {
const workspace = await this._contextService.getCompleteWorkspace();
const forceUsingSearch = !!this._environmentService.configuration.remoteAuthority;
const host: IWorkspaceContainsActivationHost = {
folders: workspace.folders.map(folder => folder.uri),
forceUsingSearch: forceUsingSearch,
exists: (path) => exists(path),
checkExists: (folders, includes, token) => this._instantiationService.invokeFunction((accessor) => checkGlobFileExists(accessor, folders, includes, token))
};
const result = await checkActivateWorkspaceContainsExtension(host, extensionDescription);
if (!result) {
return;
}
await Promise.all(
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: result.activationEvent }))
).then(() => { });
protected async _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess);
if (localProcessExtensionHost) {
await localProcessExtensionHost.deltaExtensions(toAdd, toRemove);
}
}
//#endregion
private async _scanAllLocalExtensions(): Promise<IExtensionDescription[]> {
return flatten(await Promise.all([
this._extensionScanner.scannedExtensions,

View file

@ -24,6 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IProductService } from 'vs/platform/product/common/productService';
import { URI } from 'vs/base/common/uri';
export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService {
@ -80,6 +81,13 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I
).then(undefined, () => []);
}
scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise<IExtensionDescription | null> {
return this._withChannel(
(channel, connection) => RemoteExtensionEnvironmentChannelClient.scanSingleExtension(channel, connection.remoteAuthority, isBuiltin, extensionLocation),
null
).then(undefined, () => null);
}
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined> {
return this._withChannel(
channel => RemoteExtensionEnvironmentChannelClient.getDiagnosticInfo(channel, options),

View file

@ -22,6 +22,13 @@ export interface IScanExtensionsArguments {
skipExtensions: ExtensionIdentifier[];
}
export interface IScanSingleExtensionArguments {
language: string;
remoteAuthority: string;
isBuiltin: boolean;
extensionLocation: UriComponents;
}
export interface IRemoteAgentEnvironmentDTO {
pid: number;
connectionToken: string;
@ -74,6 +81,21 @@ export class RemoteExtensionEnvironmentChannelClient {
return extensions;
}
static async scanSingleExtension(channel: IChannel, remoteAuthority: string, isBuiltin: boolean, extensionLocation: URI): Promise<IExtensionDescription | null> {
const args: IScanSingleExtensionArguments = {
language: platform.language,
remoteAuthority,
isBuiltin,
extensionLocation
};
const extension = await channel.call<IExtensionDescription | null>('scanSingleExtension', args);
if (extension) {
(<any>extension).extensionLocation = URI.revive(extension.extensionLocation);
}
return extension;
}
static getDiagnosticInfo(channel: IChannel, options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo> {
return channel.call<IDiagnosticInfo>('getDiagnosticInfo', options);
}

View file

@ -11,6 +11,7 @@ import { Event } from 'vs/base/common/event';
import { PersistenConnectionEvent as PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
export const RemoteExtensionLogFileName = 'remoteagent';
@ -34,6 +35,10 @@ export interface IRemoteAgentService {
* Scan remote extensions.
*/
scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise<IExtensionDescription[]>;
/**
* Scan a single remote extension.
*/
scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise<IExtensionDescription | null>;
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined>;
disableTelemetry(): Promise<void>;
logTelemetry(eventName: string, data?: ITelemetryData): Promise<void>;

View file

@ -187,6 +187,7 @@ export class TestNativeHostService implements INativeHostService {
async maximizeWindow(): Promise<void> { }
async unmaximizeWindow(): Promise<void> { }
async minimizeWindow(): Promise<void> { }
async setMinimumSize(width: number | undefined, height: number | undefined): Promise<void> { }
async focusWindow(options?: { windowId?: number | undefined; } | undefined): Promise<void> { }
async showMessageBox(options: Electron.MessageBoxOptions): Promise<Electron.MessageBoxReturnValue> { throw new Error('Method not implemented.'); }
async showSaveDialog(options: Electron.SaveDialogOptions): Promise<Electron.SaveDialogReturnValue> { throw new Error('Method not implemented.'); }

View file

@ -75,6 +75,7 @@ import 'vs/workbench/services/label/common/labelService';
import 'vs/workbench/services/extensionManagement/common/webExtensionsScannerService';
import 'vs/workbench/services/extensionManagement/common/extensionEnablementService';
import 'vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService';
import 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService';
import 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
import 'vs/workbench/services/notification/common/notificationService';
import 'vs/workbench/services/userDataSync/common/userDataSyncUtil';