diff --git a/.github/commands.yml b/.github/commands.yml index f20079caab8..c9279232529 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -105,7 +105,7 @@ { type: 'comment', name: 'a11ymas', - allowUsers: ['AccessibilityTestingTeam-TCS'], + allowUsers: ['AccessibilityTestingTeam-TCS', 'dixitsonali95', 'Mohini78', 'ChitrarupaSharma', 'mspatil110', 'umasarath52', 'v-umnaik'], action: 'updateLabels', addLabel: 'a11ymas' }, diff --git a/.gitignore b/.gitignore index 57bc86f3664..68834eb43ad 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ out-vscode-min/ out-vscode-reh/ out-vscode-reh-min/ out-vscode-reh-pkg/ +out-vscode-web/ +out-vscode-web-min/ +out-vscode-web-pkg/ src/vs/server resources/server build/node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json index af8b3803709..bde5d632541 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -56,5 +56,9 @@ "url": "./.vscode/cglicenses.schema.json" } ], - "git.ignoreLimitWarning": true -} \ No newline at end of file + "git.ignoreLimitWarning": true, + "remote.extensionKind": { + "msjsdiag.debugger-for-chrome": "workspace" + }, + "typescript.experimental.useSeparateSyntaxServer": true +} diff --git a/.yarnrc b/.yarnrc index e3807c54e51..441b5a2e69a 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "4.2.4" +target "4.2.5" runtime "electron" diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index df0fad7f826..1d854430864 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -5,108 +5,70 @@ Do Not Translate or Localize This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. -1. atom/language-c version 0.58.1 (https://github.com/atom/language-c) -2. atom/language-clojure version 0.22.7 (https://github.com/atom/language-clojure) -3. atom/language-coffee-script version 0.49.3 (https://github.com/atom/language-coffee-script) -4. atom/language-java version 0.31.2 (https://github.com/atom/language-java) -5. atom/language-objective-c version 0.15.0 (https://github.com/atom/language-objective-c) -6. atom/language-sass version 0.61.4 (https://github.com/atom/language-sass) -7. atom/language-shellscript version 0.26.0 (https://github.com/atom/language-shellscript) -8. atom/language-xml version 0.35.2 (https://github.com/atom/language-xml) -9. Colorsublime-Themes version 0.1.0 (https://github.com/Colorsublime/Colorsublime-Themes) -10. daaain/Handlebars version 1.8.0 (https://github.com/daaain/Handlebars) -11. davidrios/pug-tmbundle (https://github.com/davidrios/pug-tmbundle) -12. definitelytyped (https://github.com/DefinitelyTyped/DefinitelyTyped) -13. demyte/language-cshtml version 0.3.0 (https://github.com/demyte/language-cshtml) -14. Document Object Model version 4.0.0 (https://www.w3.org/DOM/) -15. dotnet/csharp-tmLanguage version 0.1.0 (https://github.com/dotnet/csharp-tmLanguage) -16. expand-abbreviation version 0.5.8 (https://github.com/emmetio/expand-abbreviation) -17. fadeevab/make.tmbundle (https://github.com/fadeevab/make.tmbundle) -18. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift) -19. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/) -20. Ikuyadeu/vscode-R version 0.5.5 (https://github.com/Ikuyadeu/vscode-R) -21. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) -22. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) -23. jeff-hykin/cpp-textmate-grammar version 1.8.15 (https://github.com/jeff-hykin/cpp-textmate-grammar) -24. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) -25. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) -26. language-docker (https://github.com/moby/moby) -27. language-go version 0.44.3 (https://github.com/atom/language-go) -28. language-less version 0.34.2 (https://github.com/atom/language-less) -29. language-php version 0.44.1 (https://github.com/atom/language-php) -30. language-rust version 0.4.12 (https://github.com/zargony/atom-language-rust) -31. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) -32. marked version 0.6.2 (https://github.com/markedjs/marked) -33. mdn-data version 1.1.12 (https://github.com/mdn/data) -34. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage) -35. Microsoft/vscode-JSON.tmLanguage (https://github.com/Microsoft/vscode-JSON.tmLanguage) -36. Microsoft/vscode-mssql version 1.4.0 (https://github.com/Microsoft/vscode-mssql) -37. mmims/language-batchfile version 0.7.5 (https://github.com/mmims/language-batchfile) -38. octicons version 8.3.0 (https://github.com/primer/octicons) -39. octref/language-css version 0.42.11 (https://github.com/octref/language-css) -40. PowerShell/EditorSyntax (https://github.com/powershell/editorsyntax) -41. promise-polyfill version 8.0.0 (https://github.com/taylorhakes/promise-polyfill) -42. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) -43. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) -44. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) -45. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) -46. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) -47. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) -48. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) -49. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) -50. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) -51. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) -52. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) -53. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) -54. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) -55. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) -56. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) -57. TypeScript-TmLanguage version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage) -58. TypeScript-TmLanguage version 1.0.0 (https://github.com/Microsoft/TypeScript-TmLanguage) -59. Unicode version 12.0.0 (http://www.unicode.org/) -60. vscode-logfile-highlighter version 2.4.1 (https://github.com/emilast/vscode-logfile-highlighter) -61. vscode-octicons-font version 1.3.0 (https://github.com/Microsoft/vscode-octicons-font) -62. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) -63. Web Background Synchronization (https://github.com/WICG/BackgroundSync) +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.2 (https://github.com/atom/language-java) +4. atom/language-sass version 0.61.4 (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) +7. Colorsublime-Themes version 0.1.0 (https://github.com/Colorsublime/Colorsublime-Themes) +8. daaain/Handlebars version 1.8.0 (https://github.com/daaain/Handlebars) +9. davidrios/pug-tmbundle (https://github.com/davidrios/pug-tmbundle) +10. definitelytyped (https://github.com/DefinitelyTyped/DefinitelyTyped) +11. demyte/language-cshtml version 0.3.0 (https://github.com/demyte/language-cshtml) +12. Document Object Model version 4.0.0 (https://www.w3.org/DOM/) +13. dotnet/csharp-tmLanguage version 0.1.0 (https://github.com/dotnet/csharp-tmLanguage) +14. expand-abbreviation version 0.5.8 (https://github.com/emmetio/expand-abbreviation) +15. fadeevab/make.tmbundle (https://github.com/fadeevab/make.tmbundle) +16. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift) +17. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/) +18. Ikuyadeu/vscode-R version 0.5.5 (https://github.com/Ikuyadeu/vscode-R) +19. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) +20. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) +21. jeff-hykin/cpp-textmate-grammar version 1.11.0 (https://github.com/jeff-hykin/cpp-textmate-grammar) +22. jeff-hykin/cpp-textmate-grammar version 1.11.7 (https://github.com/jeff-hykin/cpp-textmate-grammar) +23. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) +24. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) +25. language-docker (https://github.com/moby/moby) +26. language-go version 0.44.3 (https://github.com/atom/language-go) +27. language-less version 0.34.2 (https://github.com/atom/language-less) +28. language-php version 0.44.1 (https://github.com/atom/language-php) +29. language-rust version 0.4.12 (https://github.com/zargony/atom-language-rust) +30. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) +31. marked version 0.6.2 (https://github.com/markedjs/marked) +32. mdn-data version 1.1.12 (https://github.com/mdn/data) +33. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage) +34. Microsoft/vscode-JSON.tmLanguage (https://github.com/Microsoft/vscode-JSON.tmLanguage) +35. Microsoft/vscode-mssql version 1.4.0 (https://github.com/Microsoft/vscode-mssql) +36. mmims/language-batchfile version 0.7.5 (https://github.com/mmims/language-batchfile) +37. octicons version 8.3.0 (https://github.com/primer/octicons) +38. octref/language-css version 0.42.11 (https://github.com/octref/language-css) +39. PowerShell/EditorSyntax (https://github.com/powershell/editorsyntax) +40. promise-polyfill version 8.0.0 (https://github.com/taylorhakes/promise-polyfill) +41. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) +42. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) +43. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) +44. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) +45. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) +46. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) +47. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) +48. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) +49. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) +50. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) +51. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) +52. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) +53. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) +54. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) +55. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) +56. TypeScript-TmLanguage version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage) +57. TypeScript-TmLanguage version 1.0.0 (https://github.com/Microsoft/TypeScript-TmLanguage) +58. Unicode version 12.0.0 (http://www.unicode.org/) +59. vscode-logfile-highlighter version 2.4.1 (https://github.com/emilast/vscode-logfile-highlighter) +60. vscode-octicons-font version 1.3.1 (https://github.com/Microsoft/vscode-octicons-font) +61. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) +62. Web Background Synchronization (https://github.com/WICG/BackgroundSync) -%% atom/language-c NOTICES AND INFORMATION BEGIN HERE -========================================= -The MIT License (MIT) - -Copyright (c) 2014 GitHub Inc. - -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. - - -This package was derived from a TextMate bundle located at -https://github.com/textmate/c.tmbundle and distributed under the following -license, located in `README.mdown`: - -Permission to copy, use, modify, sell and distribute this -software is granted. This software is provided "as is" without -express or implied warranty, and with no claim as to its -suitability for any purpose. -========================================= -END OF atom/language-c NOTICES AND INFORMATION - %% atom/language-clojure NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) 2014 GitHub Inc. @@ -251,43 +213,6 @@ suitability for any purpose. ========================================= END OF atom/language-java NOTICES AND INFORMATION -%% atom/language-objective-c NOTICES AND INFORMATION BEGIN HERE -========================================= -The MIT License (MIT) - -Copyright (c) 2014 GitHub Inc. - -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. - - -This package was derived from a TextMate bundle located at -https://github.com/textmate/objective-c.tmbundle and distributed under the following -license, located in `README.mdown`: - -Permission to copy, use, modify, sell and distribute this -software is granted. This software is provided "as is" without -express or implied warranty, and with no claim as to its -suitability for any purpose. -========================================= -END OF atom/language-objective-c NOTICES AND INFORMATION - %% atom/language-sass NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 724939da318..62113c8f3b3 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -151,13 +151,6 @@ async function publish(commit: string, quality: string, platform: string, type: const queuedBy = process.env['BUILD_QUEUEDBY']!; const sourceBranch = process.env['BUILD_SOURCEBRANCH']!; - const isReleased = ( - // Insiders: nightly build from master - (quality === 'insider' && /^master$|^refs\/heads\/master$/.test(sourceBranch) && /Project Collection Service Accounts|Microsoft.VisualStudio.Services.TFS/.test(queuedBy)) || - - // Exploration: any build from electron-4.0.x branch - (quality === 'exploration' && /^electron-4.0.x$|^refs\/heads\/electron-4.0.x$/.test(sourceBranch)) - ); console.log('Publishing...'); console.log('Quality:', quality); @@ -167,7 +160,6 @@ async function publish(commit: string, quality: string, platform: string, type: console.log('Version:', version); console.log('Commit:', commit); console.log('Is Update:', isUpdate); - console.log('Is Released:', isReleased); console.log('File:', file); const stat = await new Promise((c, e) => fs.stat(file, (err, stat) => err ? e(err) : c(stat))); @@ -226,7 +218,7 @@ async function publish(commit: string, quality: string, platform: string, type: id: commit, timestamp: (new Date()).getTime(), version, - isReleased: config.frozen ? false : isReleased, + isReleased: false, sourceBranch, queuedBy, assets: [] as Array, @@ -245,11 +237,6 @@ async function publish(commit: string, quality: string, platform: string, type: } function main(): void { - if (process.env['VSCODE_BUILD_SKIP_PUBLISH']) { - console.warn('Skipping publish due to VSCODE_BUILD_SKIP_PUBLISH'); - return; - } - const commit = process.env['BUILD_SOURCEVERSION']; if (!commit) { diff --git a/build/azure-pipelines/common/release.ts b/build/azure-pipelines/common/release.ts new file mode 100644 index 00000000000..1220bd62e68 --- /dev/null +++ b/build/azure-pipelines/common/release.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { DocumentClient } from 'documentdb'; + +interface Config { + id: string; + frozen: boolean; +} + +function createDefaultConfig(quality: string): Config { + return { + id: quality, + frozen: false + }; +} + +function getConfig(quality: string): Promise { + const client = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const collection = 'dbs/builds/colls/config'; + const query = { + query: `SELECT TOP 1 * FROM c WHERE c.id = @quality`, + parameters: [ + { name: '@quality', value: quality } + ] + }; + + return new Promise((c, e) => { + client.queryDocuments(collection, query).toArray((err, results) => { + if (err && err.code !== 409) { return e(err); } + + c(!results || results.length === 0 ? createDefaultConfig(quality) : results[0] as any as Config); + }); + }); +} + +function doRelease(commit: string, quality: string): Promise { + const client = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] }); + const collection = 'dbs/builds/colls/' + quality; + const query = { + query: 'SELECT TOP 1 * FROM c WHERE c.id = @id', + parameters: [{ name: '@id', value: commit }] + }; + + let updateTries = 0; + + function update(): Promise { + updateTries++; + + return new Promise((c, e) => { + client.queryDocuments(collection, query).toArray((err, results) => { + if (err) { return e(err); } + if (results.length !== 1) { return e(new Error('No documents')); } + + const release = results[0]; + release.isReleased = true; + + client.replaceDocument(release._self, release, err => { + if (err && err.code === 409 && updateTries < 5) { return c(update()); } + if (err) { return e(err); } + + console.log('Build successfully updated.'); + c(); + }); + }); + }); + } + + return update(); +} + +async function release(commit: string, quality: string): Promise { + const config = await getConfig(quality); + + console.log('Quality config:', config); + + if (config.frozen) { + console.log(`Skipping release because quality ${quality} is frozen.`); + return; + } + + await doRelease(commit, quality); +} + +function env(name: string): string { + const result = process.env[name]; + + if (!result) { + throw new Error(`Skipping release due to missing env: ${name}`); + } + + return result; +} + +async function main(): Promise { + const commit = env('BUILD_SOURCEVERSION'); + const quality = env('VSCODE_QUALITY'); + + await release(commit, quality); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/build/azure-pipelines/common/symbols.ts b/build/azure-pipelines/common/symbols.ts index a42342ce7f7..153be4f25b1 100644 --- a/build/azure-pipelines/common/symbols.ts +++ b/build/azure-pipelines/common/symbols.ts @@ -146,6 +146,10 @@ async function ensureVersionAndSymbols(options: IOptions) { // Check version does not exist console.log(`HockeyApp: checking for existing version ${options.versions.code} (${options.platform})`); const versions = await getVersions({ accessToken: options.access.hockeyAppToken, appId: options.access.hockeyAppId }); + if (!Array.isArray(versions.app_versions)) { + throw new Error(`Unexpected response: ${JSON.stringify(versions)}`); + } + if (versions.app_versions.some(v => v.version === options.versions.code)) { console.log(`HockeyApp: Returning without uploading symbols because version ${options.versions.code} (${options.platform}) was already found`); return; @@ -184,6 +188,10 @@ const hockeyAppToken = process.argv[3]; const is64 = process.argv[4] === 'x64'; const hockeyAppId = process.argv[5]; +if (process.argv.length !== 6) { + throw new Error(`HockeyApp: Unexpected number of arguments. Got ${process.argv}`); +} + let platform: Platform; if (process.platform === 'darwin') { platform = Platform.MAC_OS; @@ -211,7 +219,9 @@ if (repository && codeVersion && electronVersion && (product.quality === 'stable }).then(() => { console.log('HockeyApp: done'); }).catch(error => { - console.error(`HockeyApp: error (${error})`); + console.error(`HockeyApp: error ${error} (AppID: ${hockeyAppId})`); + + return process.exit(1); }); } else { console.log(`HockeyApp: skipping due to unexpected context (repository: ${repository}, codeVersion: ${codeVersion}, electronVersion: ${electronVersion}, quality: ${product.quality})`); diff --git a/build/azure-pipelines/common/sync-mooncake.ts b/build/azure-pipelines/common/sync-mooncake.ts index af0db530683..1eac8f34e92 100644 --- a/build/azure-pipelines/common/sync-mooncake.ts +++ b/build/azure-pipelines/common/sync-mooncake.ts @@ -153,11 +153,6 @@ async function sync(commit: string, quality: string): Promise { } function main(): void { - if (process.env['VSCODE_BUILD_SKIP_PUBLISH']) { - error('Skipping publish due to VSCODE_BUILD_SKIP_PUBLISH'); - return; - } - const commit = process.env['BUILD_SOURCEVERSION']; if (!commit) { diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index 776c1172bea..0588b0ae961 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -11,9 +11,9 @@ steps: inputs: versionSpec: "1.10.1" - script: | - yarn + yarn --frozen-lockfile displayName: Install Dependencies - condition: ne(variables['CacheRestored'], 'true') + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index f136f2b4496..bcd5bfa57ba 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -26,18 +26,53 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" + displayName: Prepare tooling + +- script: | + set -e git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" git fetch distro git merge $(node -p "require('./package.json').distro") + displayName: Merge distro - yarn +- script: | + set -e + yarn --frozen-lockfile + displayName: Install dependencies + +- script: | + set -e yarn gulp mixin + displayName: Mix in quality + +- script: | + set -e yarn gulp hygiene yarn monaco-compile-check + displayName: Run hygiene checks + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e node build/azure-pipelines/common/installDistroDependencies.js node build/azure-pipelines/common/installDistroDependencies.js remote node build/lib/builtInExtensions.js - displayName: Prepare build + displayName: Install distro dependencies and extensions + +- script: | + set -e + cd $BUILD_STAGINGDIRECTORY + git clone https://github.com/microsoft/vscode-telemetry-extractor.git + cd vscode-telemetry-extractor + git checkout 3b04aba5bfdfcca1a5426cd2c51a90d18740d0bc + npm i + npm run setup-extension-repos + node ./out/cli-extract.js --sourceDir $BUILD_SOURCESDIRECTORY --excludedDirPattern extensions --outputDir . --applyEndpoints --includeIsMeasurement --patchWebsiteEvents + node ./out/cli-extract-extensions.js --sourceDir ./src/telemetry-sources --outputDir . --applyEndpoints --includeIsMeasurement + mkdir -p $BUILD_SOURCESDIRECTORY/.build/telemetry + mv declarations-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-core.json + mv declarations-extensions-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-extensions.json + displayName: Extract Telemetry - script: | set -e @@ -52,11 +87,13 @@ steps: # APP_NAME="`ls $(agent.builddirectory)/VSCode-darwin | head -n 1`" # yarn smoketest -- --build "$(agent.builddirectory)/VSCode-darwin/$APP_NAME" displayName: Run unit tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e ./scripts/test-integration.sh --build --tfs "Integration Tests" displayName: Run integration tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e diff --git a/build/azure-pipelines/linux/build-alpine.sh b/build/azure-pipelines/linux/build-alpine.sh new file mode 100755 index 00000000000..4f01bc9a7e5 --- /dev/null +++ b/build/azure-pipelines/linux/build-alpine.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index ee47a8d4320..d1e38506f5d 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -19,9 +19,9 @@ steps: inputs: versionSpec: "1.10.1" - script: | - yarn + yarn --frozen-lockfile displayName: Install Dependencies - condition: ne(variables['CacheRestored'], 'true') + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' diff --git a/build/azure-pipelines/linux/prebuild-alpine.sh b/build/azure-pipelines/linux/prebuild-alpine.sh new file mode 100755 index 00000000000..4f01bc9a7e5 --- /dev/null +++ b/build/azure-pipelines/linux/prebuild-alpine.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/product-build-linux-alpine.yml b/build/azure-pipelines/linux/product-build-linux-alpine.yml new file mode 100644 index 00000000000..5bce791dcf6 --- /dev/null +++ b/build/azure-pipelines/linux/product-build-linux-alpine.yml @@ -0,0 +1,85 @@ +steps: +- task: NodeTool@0 + inputs: + versionSpec: "10.15.1" + +- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.10.1" + +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + +- task: Docker@1 + displayName: 'Pull image' + inputs: + azureSubscriptionEndpoint: 'vscode-builds-subscription' + azureContainerRegistry: vscodehub.azurecr.io + command: 'Run an image' + imageName: 'vscode-linux-build-agent:alpine' + containerCommand: uname + +- script: | + set -e + + cat << EOF > ~/.netrc + machine monacotools.visualstudio.com + password $(devops-pat) + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF + + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + displayName: Prepare tooling + +- script: | + set -e + git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" + git fetch distro + git merge $(node -p "require('./package.json').distro") + displayName: Merge distro + +- script: | + set -e + CHILD_CONCURRENCY=1 yarn --frozen-lockfile + displayName: Install dependencies + +- script: | + set -e + yarn gulp mixin + displayName: Mix in quality + +- script: | + set -e + yarn gulp hygiene + yarn monaco-compile-check + displayName: Run hygiene checks + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e + ./build/azure-pipelines/linux/prebuild-alpine.sh + displayName: Prepare build + +- script: | + set -e + ./build/azure-pipelines/linux/build-alpine.sh + displayName: Build + +- script: | + set -e + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + VSCODE_HOCKEYAPP_TOKEN="$(vscode-hockeyapp-token)" \ + ./build/azure-pipelines/linux/publish-alpine.sh + displayName: Publish + +- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + continueOnError: true \ No newline at end of file diff --git a/build/azure-pipelines/linux/product-build-linux-arm.yml b/build/azure-pipelines/linux/product-build-linux-arm.yml index 55350cce295..6aac72535bc 100644 --- a/build/azure-pipelines/linux/product-build-linux-arm.yml +++ b/build/azure-pipelines/linux/product-build-linux-arm.yml @@ -35,16 +35,36 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" + displayName: Prepare tooling + +- script: | + set -e git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" git fetch distro git merge $(node -p "require('./package.json').distro") + displayName: Merge distro - CHILD_CONCURRENCY=1 yarn +- script: | + set -e + CHILD_CONCURRENCY=1 yarn --frozen-lockfile + displayName: Install dependencies + +- script: | + set -e yarn gulp mixin + displayName: Mix in quality + +- script: | + set -e yarn gulp hygiene yarn monaco-compile-check + displayName: Run hygiene checks + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e ./build/azure-pipelines/linux/prebuild-arm.sh - displayName: Prepare build + displayName: Prebuild - script: | set -e diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index c1386b93f71..bdd1064a8c9 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -27,18 +27,53 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" + displayName: Prepare tooling + +- script: | + set -e git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" git fetch distro git merge $(node -p "require('./package.json').distro") + displayName: Merge distro - CHILD_CONCURRENCY=1 yarn +- script: | + set -e + CHILD_CONCURRENCY=1 yarn --frozen-lockfile + displayName: Install dependencies + +- script: | + set -e yarn gulp mixin + displayName: Mix in quality + +- script: | + set -e yarn gulp hygiene yarn monaco-compile-check + displayName: Run hygiene checks + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e node build/azure-pipelines/common/installDistroDependencies.js node build/azure-pipelines/common/installDistroDependencies.js remote node build/lib/builtInExtensions.js - displayName: Prepare build + displayName: Install distro dependencies and extensions + +- script: | + set -e + cd $BUILD_STAGINGDIRECTORY + git clone https://github.com/microsoft/vscode-telemetry-extractor.git + cd vscode-telemetry-extractor + git checkout 3b04aba5bfdfcca1a5426cd2c51a90d18740d0bc + npm i + npm run setup-extension-repos + node ./out/cli-extract.js --sourceDir $BUILD_SOURCESDIRECTORY --excludedDirPattern extensions --outputDir . --applyEndpoints --includeIsMeasurement --patchWebsiteEvents + node ./out/cli-extract-extensions.js --sourceDir ./src/telemetry-sources --outputDir . --applyEndpoints --includeIsMeasurement + mkdir -p $BUILD_SOURCESDIRECTORY/.build/telemetry + mv declarations-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-core.json + mv declarations-extensions-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-extensions.json + displayName: Extract Telemetry - script: | set -e @@ -56,6 +91,7 @@ steps: DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests" # yarn smoketest -- --build "$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)" displayName: Run unit tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e diff --git a/build/azure-pipelines/linux/publish-alpine.sh b/build/azure-pipelines/linux/publish-alpine.sh new file mode 100755 index 00000000000..4f01bc9a7e5 --- /dev/null +++ b/build/azure-pipelines/linux/publish-alpine.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 7092be34b68..2e55abb4719 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -9,6 +9,7 @@ resources: jobs: - job: Windows condition: eq(variables['VSCODE_BUILD_WIN32'], 'true') + timeoutInMinutes: 120 pool: vmImage: VS2017-Win2016 variables: @@ -18,6 +19,7 @@ jobs: - job: Windows32 condition: eq(variables['VSCODE_BUILD_WIN32_32BIT'], 'true') + timeoutInMinutes: 120 pool: vmImage: VS2017-Win2016 variables: @@ -27,6 +29,7 @@ jobs: - job: Linux condition: eq(variables['VSCODE_BUILD_LINUX'], 'true') + timeoutInMinutes: 120 pool: vmImage: 'Ubuntu-16.04' variables: @@ -37,6 +40,7 @@ jobs: - job: LinuxSnap condition: eq(variables['VSCODE_BUILD_LINUX'], 'true') + timeoutInMinutes: 120 pool: vmImage: 'Ubuntu-16.04' variables: @@ -47,7 +51,8 @@ jobs: - template: linux/snap-build-linux.yml - job: LinuxArmhf - condition: eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true') + condition: and(eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true'), ne(variables['VSCODE_QUALITY'], 'stable')) + timeoutInMinutes: 120 pool: vmImage: 'Ubuntu-16.04' variables: @@ -55,13 +60,39 @@ jobs: steps: - template: linux/product-build-linux-arm.yml +- job: LinuxAlpine + condition: and(eq(variables['VSCODE_BUILD_LINUX_ALPINE'], 'true'), ne(variables['VSCODE_QUALITY'], 'stable')) + timeoutInMinutes: 120 + pool: + vmImage: 'Ubuntu-16.04' + variables: + VSCODE_ARCH: alpine + steps: + - template: linux/product-build-linux-alpine.yml + - job: macOS condition: eq(variables['VSCODE_BUILD_MACOS'], 'true') + timeoutInMinutes: 120 pool: vmImage: macOS 10.13 steps: - template: darwin/product-build-darwin.yml +- job: Release + condition: and(succeeded(), or(eq(variables['VSCODE_RELEASE'], 'true'), and(eq(variables['VSCODE_QUALITY'], 'insider'), eq(variables['Build.Reason'], 'Schedule')), and(eq(variables['VSCODE_QUALITY'], 'exploration'), eq(variables['Build.SourceBranch'], 'refs/heads/electron-4.0.x')))) + pool: + vmImage: 'Ubuntu-16.04' + dependsOn: + - Windows + - Windows32 + - Linux + - LinuxSnap + - LinuxArmhf + - LinuxAlpine + - macOS + steps: + - template: release.yml + - job: Mooncake pool: vmImage: 'Ubuntu-16.04' @@ -72,6 +103,7 @@ jobs: - Linux - LinuxSnap - LinuxArmhf + - LinuxAlpine - macOS steps: - template: sync-mooncake.yml \ No newline at end of file diff --git a/build/azure-pipelines/release.yml b/build/azure-pipelines/release.yml new file mode 100644 index 00000000000..eee54a1d8b7 --- /dev/null +++ b/build/azure-pipelines/release.yml @@ -0,0 +1,22 @@ +steps: +- task: NodeTool@0 + inputs: + versionSpec: "10.x" + +- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.x" + +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + +- script: | + set -e + + (cd build ; yarn) + + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + node build/azure-pipelines/common/release.js diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index 36336276814..7f10e74d7c5 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -15,9 +15,9 @@ steps: targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: '$(ArtifactFeed)' - powershell: | - yarn + yarn --frozen-lockfile displayName: Install Dependencies - condition: ne(variables['CacheRestored'], 'true') + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 339bdde1b9c..09a0f7af93b 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -22,23 +22,64 @@ steps: . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" "machine monacotools.visualstudio.com`npassword $(devops-pat)`nmachine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII - $env:npm_config_arch="$(VSCODE_ARCH)" - $env:CHILD_CONCURRENCY="1" exec { git config user.email "vscode@microsoft.com" } exec { git config user.name "VSCode" } + displayName: Prepare tooling + +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" exec { git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" } exec { git fetch distro } exec { git merge $(node -p "require('./package.json').distro") } + displayName: Merge distro - exec { yarn } +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:npm_config_arch="$(VSCODE_ARCH)" + $env:CHILD_CONCURRENCY="1" + exec { yarn --frozen-lockfile } + displayName: Install dependencies + +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" exec { yarn gulp mixin } + displayName: Mix in quality + +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" exec { yarn gulp hygiene } exec { yarn monaco-compile-check } + displayName: Run hygiene checks + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" exec { node build/azure-pipelines/common/installDistroDependencies.js } exec { node build/azure-pipelines/common/installDistroDependencies.js remote } exec { node build/lib/builtInExtensions.js } - displayName: Prepare build + displayName: Install distro dependencies and extensions + +- powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + cd $env:BUILD_STAGINGDIRECTORY + git clone https://github.com/microsoft/vscode-telemetry-extractor.git + cd vscode-telemetry-extractor + git checkout 3b04aba5bfdfcca1a5426cd2c51a90d18740d0bc + npm i + npm run setup-extension-repos + node .\out\cli-extract.js --sourceDir $env:BUILD_SOURCESDIRECTORY --excludedDirPattern extensions --outputDir . --applyEndpoints --includeIsMeasurement --patchWebsiteEvents + node .\out\cli-extract-extensions.js --sourceDir .\src\telemetry-sources --outputDir . --applyEndpoints --includeIsMeasurement + mkdir $env:BUILD_SOURCESDIRECTORY\.build\telemetry -ea 0 + mv declarations-resolved.json $env:BUILD_SOURCESDIRECTORY\.build\telemetry\telemetry-core.json + mv declarations-extensions-resolved.json $env:BUILD_SOURCESDIRECTORY\.build\telemetry\telemetry-extensions.json + displayName: Extract Telemetry - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -52,8 +93,8 @@ steps: $ErrorActionPreference = "Stop" exec { yarn gulp "electron-$(VSCODE_ARCH)" } exec { .\scripts\test.bat --build --tfs "Unit Tests" } - # yarn smoketest -- --build "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" displayName: Run unit tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -61,6 +102,7 @@ steps: exec { yarn gulp "electron-$(VSCODE_ARCH)" } exec { .\scripts\test-integration.bat --build --tfs "Integration Tests" } displayName: Run integration tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: @@ -142,6 +184,7 @@ steps: $env:AZURE_STORAGE_ACCESS_KEY_2 = "$(vscode-storage-key)" $env:AZURE_DOCUMENTDB_MASTERKEY = "$(builds-docdb-key-readwrite)" $env:VSCODE_HOCKEYAPP_TOKEN = "$(vscode-hockeyapp-token)" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" .\build\azure-pipelines\win32\publish.ps1 displayName: Publish diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index ed6d6c2662d..7f1c9fd2f6f 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.35.2", + "version": "1.35.3", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 50082693334..b49bc069366 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -175,6 +175,17 @@ gulp.task('tslint', () => { function hygiene(some) { let errorCount = 0; + const productJson = es.through(function (file) { + const product = JSON.parse(file.contents.toString('utf8')); + + if (product.extensionsGallery) { + console.error('product.json: Contains "extensionsGallery"'); + errorCount++; + } + + this.emit('data', file); + }); + const indentation = es.through(function (file) { const lines = file.contents.toString('utf8').split(/\r\n|\r|\n/); file.__lines = lines; @@ -258,8 +269,13 @@ function hygiene(some) { input = some; } + const productJsonFilter = filter('product.json', { restore: true }); + const result = input .pipe(filter(f => !f.stat.isDirectory())) + .pipe(productJsonFilter) + .pipe(process.env['BUILD_SOURCEVERSION'] ? es.through() : productJson) + .pipe(productJsonFilter.restore) .pipe(filter(indentationFilter)) .pipe(indentation) .pipe(filter(copyrightFilter)) diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 83eebc9d69d..2773a8cb430 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -19,6 +19,8 @@ const untar = require('gulp-untar'); const File = require('vinyl'); const fs = require('fs'); +const cp = require('child_process'); + const REPO_ROOT = path.dirname(__dirname); const noop = () => { return Promise.resolve(); }; @@ -28,6 +30,7 @@ gulp.task('vscode-reh-win32-x64-min', noop); gulp.task('vscode-reh-darwin-min', noop); gulp.task('vscode-reh-linux-x64-min', noop); gulp.task('vscode-reh-linux-armhf-min', noop); +gulp.task('vscode-reh-linux-alpine-min', noop); function getNodeVersion() { @@ -84,6 +87,18 @@ function nodejs(platform, arch) { ); } + if (arch === 'alpine') { + return es.readArray([ + new File({ + path: 'node', + contents: cp.execSync(`docker run --rm node:${VERSION}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' }), + stat: { + mode: parseInt('755', 8) + } + }) + ]); + } + if (platform === 'darwin') { arch = 'x64'; } @@ -115,3 +130,33 @@ function nodejs(platform, arch) { })) ); } + +function mixinServer(watch) { + const packageJSONPath = path.join(path.dirname(__dirname), 'package.json'); + function exec(cmdLine) { + console.log(cmdLine); + cp.execSync(cmdLine, { stdio: "inherit" }); + } + function checkout() { + const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath).toString()); + exec('git fetch distro'); + exec(`git checkout ${packageJSON['distro']} -- src/vs/server resources/server`); + exec('git reset HEAD src/vs/server resources/server'); + } + checkout(); + if (watch) { + console.log('Enter watch mode (observing package.json)'); + const watcher = fs.watch(packageJSONPath); + watcher.addListener('change', () => { + try { + checkout(); + } catch (e) { + console.log(e); + } + }); + } + return Promise.resolve(); +} + +gulp.task(task.define('mixin-server', () => mixinServer(false))); +gulp.task(task.define('mixin-server-watch', () => mixinServer(true))); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 9c32df0bf81..0673fdb0306 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -315,6 +315,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op // TODO the API should be copied to `out` during compile, not here const api = gulp.src('src/vs/vscode.d.ts').pipe(rename('out/vs/vscode.d.ts')); + const telemetry = gulp.src('.build/telemetry/**', { base: '.build/telemetry', dot: true }); + const depsSrc = [ ..._.flatten(productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`])), // @ts-ignore JSON checking: dependencies is optional @@ -327,10 +329,11 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*', '**/node-pty/build/Release/*'], 'app/node_modules.asar')); let all = es.merge( - packageJsonStream, + packageJsonStream, productJsonStream, license, api, + telemetry, sources, deps ); @@ -380,7 +383,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(util.skipDirectories()) .pipe(util.fixWin32DirectoryPermissions()) .pipe(electron(_.extend({}, config, { platform, arch, ffmpegChromium: true }))) - .pipe(filter(['**', '!LICENSE', '!LICENSES.chromium.html', '!version'])); + .pipe(filter(['**', '!LICENSE', '!LICENSES.chromium.html', '!version'], { dot: true })); // result = es.merge(result, gulp.src('resources/completions/**', { base: '.' })); diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 46543316de9..b20efebdb62 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -25,7 +25,7 @@ const zipDir = arch => path.join(repoPath, '.build', `win32-${arch}`, 'archive') const zipPath = arch => path.join(zipDir(arch), `VSCode-win32-${arch}.zip`); const setupDir = (arch, target) => path.join(repoPath, '.build', `win32-${arch}`, `${target}-setup`); const issPath = path.join(__dirname, 'win32', 'code.iss'); -const innoSetupPath = path.join(path.dirname(path.dirname(require.resolve('innosetup-compiler'))), 'bin', 'ISCC.exe'); +const innoSetupPath = path.join(path.dirname(path.dirname(require.resolve('innosetup'))), 'bin', 'ISCC.exe'); const signPS1 = path.join(repoPath, 'build', 'azure-pipelines', 'win32', 'sign.ps1'); function packageInnoSetup(iss, options, cb) { diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index edc5e35ade3..80a4f0eeb5c 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -20,13 +20,10 @@ function yarnInstall(location, opts) { const raw = process.env['npm_config_argv'] || '{}'; const argv = JSON.parse(raw); const original = argv.original || []; - const args = ['install']; + const args = original.filter(arg => arg === '--ignore-optional' || arg === '--frozen-lockfile'); - if (original.indexOf('--ignore-optional') > -1) { - args.push('--ignore-optional'); - } - - console.log('Installing dependencies in \'%s\'.', location); + console.log(`Installing dependencies in ${location}...`); + console.log(`$ yarn ${args.join(' ')}`); const result = cp.spawnSync(yarn, args, opts); if (result.error || result.status !== 0) { diff --git a/build/win32/code.iss b/build/win32/code.iss index ea31a50c9bf..831b31a3c71 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1034,7 +1034,7 @@ begin AltArch := '32'; end; - if not Result then begin + if not Result and not WizardSilent() then begin MsgBox('Please uninstall the ' + AltArch + '-bit version of {#NameShort} before installing this ' + ThisArch + '-bit version.', mbInformation, MB_OK); end; end; diff --git a/cglicenses.json b/cglicenses.json index 3d96813f3ef..469cdedc9ad 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -6,694 +6,742 @@ // DO NOT EDIT THIS FILE UNLESS THE OSS TOOL INDICATES THAT YOU SHOULD. // [ -{ - // Reason: The license at https://github.com/aadsm/jschardet/blob/master/LICENSE - // does not include a clear Copyright statement and does not credit authors. - "name": "jschardet", - "licenseDetail": [ - "Chardet was originally ported from C++ by Mark Pilgrim. It is now maintained", - " by Dan Blanchard and Ian Cordasco, and was formerly maintained by Erik Rose.", - " JSChardet was ported from python to JavaScript by António Afonso ", - " (https://github.com/aadsm/jschardet) and transformed into an npm package by ", - "Markus Ast (https://github.com/brainafk)", - "", - "GNU LESSER GENERAL PUBLIC LICENSE", - "\t\t Version 2.1, February 1999", - "", - " Copyright (C) 1991,", - "1999 Free Software Foundation, Inc.", - " 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA", - " Everyone is permitted to copy and distribute verbatim copies", - " of this license document, but changing it is not allowed.", - "", - "[This is the first released version of the Lesser GPL. It also counts", - " as the successor of the GNU Library Public License, version 2, hence", - " the version number 2.1.", - "]", - "", - "\t\t\t Preamble", - "", - " The licenses for most software are designed to take away your", - "freedom to share and change it. By contrast, the GNU General Public", - "Licenses are intended to guarantee your freedom to share and change", - "free software--to make sure the software is free for all its users.", - "", - " This license, the Lesser General Public License, applies to some", - "specially designated software packages--typically libraries--of the", - "Free Software Foundation and other authors who decide to use it. You", - "can use it too, but we suggest you first think carefully about whether", - "this license or the ordinary General Public License is the better", - "strategy to use in any particular case, based on the explanations below.", - "", - " When we speak of free software, we are referring to freedom of use,", - "not price. Our General Public Licenses are designed to make sure that", - "you have the freedom to distribute copies of free software (and charge", - "for this service if you wish); that you receive source code or can get", - "it if you want it; that you can change the software and use pieces of", - "it in new free programs; and that you are informed that you can do", - "these things.", - "", - " To protect your rights, we need to make restrictions that forbid", - "distributors to deny you these rights or to ask you to surrender these", - "rights. These restrictions translate to certain responsibilities for", - "you if you distribute copies of the library or if you modify it.", - "", - " For example, if you distribute copies of the library, whether gratis", - "or for a fee, you must give the recipients all the rights that we gave", - "you. You must make sure that they, too, receive or can get the source", - "code. If you link other code with the library, you must provide", - "complete object files to the recipients, so that they can relink them", - "with the library after making changes to the library and recompiling", - "it. And you must show them these terms so they know their rights.", - "", - " We protect your rights with a two-step method: (1) we copyright the", - "library, and (2) we offer you this license, which gives you legal", - "permission to copy, distribute and/or modify the library.", - "", - " To protect each distributor, we want to make it very clear that", - "there is no warranty for the free library. Also, if the library is", - "modified by someone else and passed on, the recipients should know", - "that what they have is not the original version, so that the original", - "author's reputation will not be affected by problems that might be", - "introduced by others.", - "", - " Finally, software patents pose a constant threat to the existence of", - "any free program. We wish to make sure that a company cannot", - "effectively restrict the users of a free program by obtaining a", - "restrictive license from a patent holder. Therefore, we insist that", - "any patent license obtained for a version of the library must be", - "consistent with the full freedom of use specified in this license.", - "", - " Most GNU software, including some libraries, is covered by the", - "ordinary GNU General Public License. This license, the GNU Lesser", - "General Public License, applies to certain designated libraries, and", - "is quite different from the ordinary General Public License. We use", - "this license for certain libraries in order to permit linking those", - "libraries into non-free programs.", - "", - " When a program is linked with a library, whether statically or using", - "a shared library, the combination of the two is legally speaking a", - "combined work, a derivative of the original library. The ordinary", - "General Public License therefore permits such linking only if the", - "entire combination fits its criteria of freedom. The Lesser General", - "Public License permits more lax criteria for linking other code with", - "the library.", - "", - " We call this license the \"Lesser\" General Public License because it", - "does Less to protect the user's freedom than the ordinary General", - "Public License. It also provides other free software developers Less", - "of an advantage over competing non-free programs. These disadvantages", - "are the reason we use the ordinary General Public License for many", - "libraries. However, the Lesser license provides advantages in certain", - "special circumstances.", - "", - " For example, on rare occasions, there may be a special need to", - "encourage the widest possible use of a certain library, so that it becomes", - "a de-facto standard. To achieve this, non-free programs must be", - "allowed to use the library. A more frequent case is that a free", - "library does the same job as widely used non-free libraries. In this", - "case, there is little to gain by limiting the free library to free", - "software only, so we use the Lesser General Public License.", - "", - " In other cases, permission to use a particular library in non-free", - "programs enables a greater number of people to use a large body of", - "free software. For example, permission to use the GNU C Library in", - "non-free programs enables many more people to use the whole GNU", - "operating system, as well as its variant, the GNU/Linux operating", - "system.", - "", - " Although the Lesser General Public License is Less protective of the", - "users' freedom, it does ensure that the user of a program that is", - "linked with the Library has the freedom and the wherewithal to run", - "that program using a modified version of the Library.", - "", - " The precise terms and conditions for copying, distribution and", - "modification follow. Pay close attention to the difference between a", - "\"work based on the library\" and a \"work that uses the library\". The", - "former contains code derived from the library, whereas the latter must", - "be combined with the library in order to run.", - "", - "\t\t GNU LESSER GENERAL PUBLIC LICENSE", - " TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION", - "", - " 0. This License Agreement applies to any software library or other", - "program which contains a notice placed by the copyright holder or", - "other authorized party saying it may be distributed under the terms of", - "this Lesser General Public License (also called \"this License\").", - "Each licensee is addressed as \"you\".", - "", - " A \"library\" means a collection of software functions and/or data", - "prepared so as to be conveniently linked with application programs", - "(which use some of those functions and data) to form executables.", - "", - " The \"Library\", below, refers to any such software library or work", - "which has been distributed under these terms. A \"work based on the", - "Library\" means either the Library or any derivative work under", - "copyright law: that is to say, a work containing the Library or a", - "portion of it, either verbatim or with modifications and/or translated", - "straightforwardly into another language. (Hereinafter, translation is", - "included without limitation in the term \"modification\".)", - "", - " \"Source code\" for a work means the preferred form of the work for", - "making modifications to it. For a library, complete source code means", - "all the source code for all modules it contains, plus any associated", - "interface definition files, plus the scripts used to control compilation", - "and installation of the library.", - "", - " Activities other than copying, distribution and modification are not", - "covered by this License; they are outside its scope. The act of", - "running a program using the Library is not restricted, and output from", - "such a program is covered only if its contents constitute a work based", - "on the Library (independent of the use of the Library in a tool for", - "writing it). Whether that is true depends on what the Library does", - "and what the program that uses the Library does.", - "", - " 1. You may copy and distribute verbatim copies of the Library's", - "complete source code as you receive it, in any medium, provided that", - "you conspicuously and appropriately publish on each copy an", - "appropriate copyright notice and disclaimer of warranty; keep intact", - "all the notices that refer to this License and to the absence of any", - "warranty; and distribute a copy of this License along with the", - "Library.", - "", - " You may charge a fee for the physical act of transferring a copy,", - "and you may at your option offer warranty protection in exchange for a", - "fee.", - "", - " 2. You may modify your copy or copies of the Library or any portion", - "of it, thus forming a work based on the Library, and copy and", - "distribute such modifications or work under the terms of Section 1", - "above, provided that you also meet all of these conditions:", - "", - " a) The modified work must itself be a software library.", - "", - " b) You must cause the files modified to carry prominent notices", - " stating that you changed the files and the date of any change.", - "", - " c) You must cause the whole of the work to be licensed at no", - " charge to all third parties under the terms of this License.", - "", - " d) If a facility in the modified Library refers to a function or a", - " table of data to be supplied by an application program that uses", - " the facility, other than as an argument passed when the facility", - " is invoked, then you must make a good faith effort to ensure that,", - " in the event an application does not supply such function or", - " table, the facility still operates, and performs whatever part of", - " its purpose remains meaningful.", - "", - " (For example, a function in a library to compute square roots has", - " a purpose that is entirely well-defined independent of the", - " application. Therefore, Subsection 2d requires that any", - " application-supplied function or table used by this function must", - " be optional: if the application does not supply it, the square", - " root function must still compute square roots.)", - "", - "These requirements apply to the modified work as a whole. If", - "identifiable sections of that work are not derived from the Library,", - "and can be reasonably considered independent and separate works in", - "themselves, then this License, and its terms, do not apply to those", - "sections when you distribute them as separate works. But when you", - "distribute the same sections as part of a whole which is a work based", - "on the Library, the distribution of the whole must be on the terms of", - "this License, whose permissions for other licensees extend to the", - "entire whole, and thus to each and every part regardless of who wrote", - "it.", - "", - "Thus, it is not the intent of this section to claim rights or contest", - "your rights to work written entirely by you; rather, the intent is to", - "exercise the right to control the distribution of derivative or", - "collective works based on the Library.", - "", - "In addition, mere aggregation of another work not based on the Library", - "with the Library (or with a work based on the Library) on a volume of", - "a storage or distribution medium does not bring the other work under", - "the scope of this License.", - "", - " 3. You may opt to apply the terms of the ordinary GNU General Public", - "License instead of this License to a given copy of the Library. To do", - "this, you must alter all the notices that refer to this License, so", - "that they refer to the ordinary GNU General Public License, version 2,", - "instead of to this License. (If a newer version than version 2 of the", - "ordinary GNU General Public License has appeared, then you can specify", - "that version instead if you wish.) Do not make any other change in", - "these notices.", - "", - " Once this change is made in a given copy, it is irreversible for", - "that copy, so the ordinary GNU General Public License applies to all", - "subsequent copies and derivative works made from that copy.", - "", - " This option is useful when you wish to copy part of the code of", - "the Library into a program that is not a library.", - "", - " 4. You may copy and distribute the Library (or a portion or", - "derivative of it, under Section 2) in object code or executable form", - "under the terms of Sections 1 and 2 above provided that you accompany", - "it with the complete corresponding machine-readable source code, which", - "must be distributed under the terms of Sections 1 and 2 above on a", - "medium customarily used for software interchange.", - "", - " If distribution of object code is made by offering access to copy", - "from a designated place, then offering equivalent access to copy the", - "source code from the same place satisfies the requirement to", - "distribute the source code, even though third parties are not", - "compelled to copy the source along with the object code.", - "", - " 5. A program that contains no derivative of any portion of the", - "Library, but is designed to work with the Library by being compiled or", - "linked with it, is called a \"work that uses the Library\". Such a", - "work, in isolation, is not a derivative work of the Library, and", - "therefore falls outside the scope of this License.", - "", - " However, linking a \"work that uses the Library\" with the Library", - "creates an executable that is a derivative of the Library (because it", - "contains portions of the Library), rather than a \"work that uses the", - "library\". The executable is therefore covered by this License.", - "Section 6 states terms for distribution of such executables.", - "", - " When a \"work that uses the Library\" uses material from a header file", - "that is part of the Library, the object code for the work may be a", - "derivative work of the Library even though the source code is not.", - "Whether this is true is especially significant if the work can be", - "linked without the Library, or if the work is itself a library. The", - "threshold for this to be true is not precisely defined by law.", - "", - " If such an object file uses only numerical parameters, data", - "structure layouts and accessors, and small macros and small inline", - "functions (ten lines or less in length), then the use of the object", - "file is unrestricted, regardless of whether it is legally a derivative", - "work. (Executables containing this object code plus portions of the", - "Library will still fall under Section 6.)", - "", - " Otherwise, if the work is a derivative of the Library, you may", - "distribute the object code for the work under the terms of Section 6.", - "Any executables containing that work also fall under Section 6,", - "whether or not they are linked directly with the Library itself.", - "", - " 6. As an exception to the Sections above, you may also combine or", - "link a \"work that uses the Library\" with the Library to produce a", - "work containing portions of the Library, and distribute that work", - "under terms of your choice, provided that the terms permit", - "modification of the work for the customer's own use and reverse", - "engineering for debugging such modifications.", - "", - " You must give prominent notice with each copy of the work that the", - "Library is used in it and that the Library and its use are covered by", - "this License. You must supply a copy of this License. If the work", - "during execution displays copyright notices, you must include the", - "copyright notice for the Library among them, as well as a reference", - "directing the user to the copy of this License. Also, you must do one", - "of these things:", - "", - " a) Accompany the work with the complete corresponding", - " machine-readable source code for the Library including whatever", - " changes were used in the work (which must be distributed under", - " Sections 1 and 2 above); and, if the work is an executable linked", - " with the Library, with the complete machine-readable \"work that", - " uses the Library\", as object code and/or source code, so that the", - " user can modify the Library and then relink to produce a modified", - " executable containing the modified Library. (It is understood", - " that the user who changes the contents of definitions files in the", - " Library will not necessarily be able to recompile the application", - " to use the modified definitions.)", - "", - " b) Use a suitable shared library mechanism for linking with the", - " Library. A suitable mechanism is one that (1) uses at run time a", - " copy of the library already present on the user's computer system,", - " rather than copying library functions into the executable, and (2)", - " will operate properly with a modified version of the library, if", - " the user installs one, as long as the modified version is", - " interface-compatible with the version that the work was made with.", - "", - " c) Accompany the work with a written offer, valid for at", - " least three years, to give the same user the materials", - " specified in Subsection 6a, above, for a charge no more", - " than the cost of performing this distribution.", - "", - " d) If distribution of the work is made by offering access to copy", - " from a designated place, offer equivalent access to copy the above", - " specified materials from the same place.", - "", - " e) Verify that the user has already received a copy of these", - " materials or that you have already sent this user a copy.", - "", - " For an executable, the required form of the \"work that uses the", - "Library\" must include any data and utility programs needed for", - "reproducing the executable from it. However, as a special exception,", - "the materials to be distributed need not include anything that is", - "normally distributed (in either source or binary form) with the major", - "components (compiler, kernel, and so on) of the operating system on", - "which the executable runs, unless that component itself accompanies", - "the executable.", - "", - " It may happen that this requirement contradicts the license", - "restrictions of other proprietary libraries that do not normally", - "accompany the operating system. Such a contradiction means you cannot", - "use both them and the Library together in an executable that you", - "distribute.", - "", - " 7. You may place library facilities that are a work based on the", - "Library side-by-side in a single library together with other library", - "facilities not covered by this License, and distribute such a combined", - "library, provided that the separate distribution of the work based on", - "the Library and of the other library facilities is otherwise", - "permitted, and provided that you do these two things:", - "", - " a) Accompany the combined library with a copy of the same work", - " based on the Library, uncombined with any other library", - " facilities. This must be distributed under the terms of the", - " Sections above.", - "", - " b) Give prominent notice with the combined library of the fact", - " that part of it is a work based on the Library, and explaining", - " where to find the accompanying uncombined form of the same work.", - "", - " 8. You may not copy, modify, sublicense, link with, or distribute", - "the Library except as expressly provided under this License. Any", - "attempt otherwise to copy, modify, sublicense, link with, or", - "distribute the Library is void, and will automatically terminate your", - "rights under this License. However, parties who have received copies,", - "or rights, from you under this License will not have their licenses", - "terminated so long as such parties remain in full compliance.", - "", - " 9. You are not required to accept this License, since you have not", - "signed it. However, nothing else grants you permission to modify or", - "distribute the Library or its derivative works. These actions are", - "prohibited by law if you do not accept this License. Therefore, by", - "modifying or distributing the Library (or any work based on the", - "Library), you indicate your acceptance of this License to do so, and", - "all its terms and conditions for copying, distributing or modifying", - "the Library or works based on it.", - "", - " 10. Each time you redistribute the Library (or any work based on the", - "Library), the recipient automatically receives a license from the", - "original licensor to copy, distribute, link with or modify the Library", - "subject to these terms and conditions. You may not impose any further", - "restrictions on the recipients' exercise of the rights granted herein.", - "You are not responsible for enforcing compliance by third parties with", - "this License.", - "", - " 11. If, as a consequence of a court judgment or allegation of patent", - "infringement or for any other reason (not limited to patent issues),", - "conditions are imposed on you (whether by court order, agreement or", - "otherwise) that contradict the conditions of this License, they do not", - "excuse you from the conditions of this License. If you cannot", - "distribute so as to satisfy simultaneously your obligations under this", - "License and any other pertinent obligations, then as a consequence you", - "may not distribute the Library at all. For example, if a patent", - "license would not permit royalty-free redistribution of the Library by", - "all those who receive copies directly or indirectly through you, then", - "the only way you could satisfy both it and this License would be to", - "refrain entirely from distribution of the Library.", - "", - "If any portion of this section is held invalid or unenforceable under any", - "particular circumstance, the balance of the section is intended to apply,", - "and the section as a whole is intended to apply in other circumstances.", - "", - "It is not the purpose of this section to induce you to infringe any", - "patents or other property right claims or to contest validity of any", - "such claims; this section has the sole purpose of protecting the", - "integrity of the free software distribution system which is", - "implemented by public license practices. Many people have made", - "generous contributions to the wide range of software distributed", - "through that system in reliance on consistent application of that", - "system; it is up to the author/donor to decide if he or she is willing", - "to distribute software through any other system and a licensee cannot", - "impose that choice.", - "", - "This section is intended to make thoroughly clear what is believed to", - "be a consequence of the rest of this License.", - "", - " 12. If the distribution and/or use of the Library is restricted in", - "certain countries either by patents or by copyrighted interfaces, the", - "original copyright holder who places the Library under this License may add", - "an explicit geographical distribution limitation excluding those countries,", - "so that distribution is permitted only in or among countries not thus", - "excluded. In such case, this License incorporates the limitation as if", - "written in the body of this License.", - "", - " 13. The Free Software Foundation may publish revised and/or new", - "versions of the Lesser General Public License from time to time.", - "Such new versions will be similar in spirit to the present version,", - "but may differ in detail to address new problems or concerns.", - "", - "Each version is given a distinguishing version number. If the Library", - "specifies a version number of this License which applies to it and", - "\"any later version\", you have the option of following the terms and", - "conditions either of that version or of any later version published by", - "the Free Software Foundation. If the Library does not specify a", - "license version number, you may choose any version ever published by", - "the Free Software Foundation.", - "", - " 14. If you wish to incorporate parts of the Library into other free", - "programs whose distribution conditions are incompatible with these,", - "write to the author to ask for permission. For software which is", - "copyrighted by the Free Software Foundation, write to the Free", - "Software Foundation; we sometimes make exceptions for this. Our", - "decision will be guided by the two goals of preserving the free status", - "of all derivatives of our free software and of promoting the sharing", - "and reuse of software generally.", - "", - "\t\t\t NO WARRANTY", - "", - " 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO", - "WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.", - "EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR", - "OTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY", - "KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE", - "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR", - "PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE", - "LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME", - "THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.", - "", - " 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN", - "WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY", - "AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU", - "FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR", - "CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE", - "LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING", - "RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A", - "FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF", - "SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH", - "DAMAGES.", - "", - "\t\t END OF TERMS AND CONDITIONS", - "", - " How to Apply These Terms to Your New Libraries", - "", - " If you develop a new library, and you want it to be of the greatest", - "possible use to the public, we recommend making it free software that", - "everyone can redistribute and change. You can do so by permitting", - "redistribution under these terms (or, alternatively, under the terms of the", - "ordinary General Public License).", - "", - " To apply these terms, attach the following notices to the library. It is", - "safest to attach them to the start of each source file to most effectively", - "convey the exclusion of warranty; and each file should have at least the", - "\"copyright\" line and a pointer to where the full notice is found.", - "", - " ", - " Copyright (C) ", - "", - " This library is free software; you can redistribute it and/or", - " modify it under the terms of the GNU Lesser General Public", - " License as published by the Free Software Foundation; either", - " version 2.1 of the License, or (at your option) any later version.", - "", - " This library is distributed in the hope that it will be useful,", - " but WITHOUT ANY WARRANTY; without even the implied warranty of", - " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU", - " Lesser General Public License for more details.", - "", - " You should have received a copy of the GNU Lesser General Public", - " License along with this library; if not, write to the Free Software", - " Foundation, Inc.,", - "51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA", - "", - "Also add information on how to contact you by electronic and paper mail.", - "", - "You should also get your employer (if you work as a programmer) or your", - "school, if any, to sign a \"copyright disclaimer\" for the library, if", - "necessary. Here is a sample; alter the names:", - "", - " Yoyodyne, Inc., hereby disclaims all copyright interest in the", - " library `Frob' (a library for tweaking knobs) written by James Random Hacker.", - "", - " ,", - "1 April 1990", - " Ty Coon, President of Vice", - "", - "That's all there is to it!" - ] -}, -{ - // Added here because the module `parse5` has a dependency to it. - // The module `parse5` is shipped via the `extension-editing` built-in extension. - // The module `parse5` does not want to remove it https://github.com/inikulin/parse5/issues/225 - "name": "@types/node", - "licenseDetail": [ - "This project is licensed under the MIT license.", - "Copyrights are respective of each contributor listed at the beginning of each definition file.", - "", - "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." - ] -}, -{ - // We override the license that gets discovered at - // https://github.com/Microsoft/TypeScript/blob/master/LICENSE.txt - // because it does not contain a Copyright statement - "name": "typescript", - "licenseDetail": [ - "Copyright (c) Microsoft Corporation. All rights reserved.", - "", - "Apache License", - "", - "Version 2.0, January 2004", - "", - "http://www.apache.org/licenses/", - "", - "TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION", - "", - "1. Definitions.", - "", - "\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.", - "", - "\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.", - "", - "\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.", - "", - "\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.", - "", - "\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.", - "", - "\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.", - "", - "\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).", - "", - "\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.", - "", - "\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"", - "", - "\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.", - "", - "2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.", - "", - "3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.", - "", - "4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:", - "", - "You must give any other recipients of the Work or Derivative Works a copy of this License; and", - "", - "You must cause any modified files to carry prominent notices stating that You changed the files; and", - "", - "You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and", - "", - "If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.", - "", - "5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.", - "", - "6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.", - "", - "7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.", - "", - "8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.", - "", - "9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.", - "", - "END OF TERMS AND CONDITIONS" - ] -}, -{ - // This module comes in from https://github.com/Microsoft/vscode-node-debug2/blob/master/package-lock.json - "name": "@types/source-map", - "licenseDetail": [ - "This project is licensed under the MIT license.", - "Copyrights are respective of each contributor listed at the beginning of each definition file.", - "", - "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." - ] -}, -{ - "name": "tunnel-agent", - "licenseDetail": [ - "Copyright (c) tunnel-agent authors", - "", - "Apache License", - "", - "Version 2.0, January 2004", - "", - "http://www.apache.org/licenses/", - "", - "TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION", - "", - "1. Definitions.", - "", - "\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.", - "", - "\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.", - "", - "\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.", - "", - "\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.", - "", - "\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.", - "", - "\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.", - "", - "\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).", - "", - "\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.", - "", - "\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"", - "", - "\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.", - "", - "2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.", - "", - "3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.", - "", - "4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:", - "", - "You must give any other recipients of the Work or Derivative Works a copy of this License; and", - "", - "You must cause any modified files to carry prominent notices stating that You changed the files; and", - "", - "You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and", - "", - "If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.", - "", - "5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.", - "", - "6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.", - "", - "7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.", - "", - "8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.", - "", - "9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.", - "", - "END OF TERMS AND CONDITIONS" - ] -}, -{ - // Waiting for https://github.com/segmentio/noop-logger/issues/2 - "name": "noop-logger", - "licenseDetail": [ - "This project is licensed under the MIT license.", - "Copyrights are respective of each contributor listed at the beginning of each definition file.", - "", - "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." - ] -} -] \ No newline at end of file + { + // Reason: The license at https://github.com/aadsm/jschardet/blob/master/LICENSE + // does not include a clear Copyright statement and does not credit authors. + "name": "jschardet", + "licenseDetail": [ + "Chardet was originally ported from C++ by Mark Pilgrim. It is now maintained", + " by Dan Blanchard and Ian Cordasco, and was formerly maintained by Erik Rose.", + " JSChardet was ported from python to JavaScript by António Afonso ", + " (https://github.com/aadsm/jschardet) and transformed into an npm package by ", + "Markus Ast (https://github.com/brainafk)", + "", + "GNU LESSER GENERAL PUBLIC LICENSE", + "\t\t Version 2.1, February 1999", + "", + " Copyright (C) 1991,", + "1999 Free Software Foundation, Inc.", + " 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA", + " Everyone is permitted to copy and distribute verbatim copies", + " of this license document, but changing it is not allowed.", + "", + "[This is the first released version of the Lesser GPL. It also counts", + " as the successor of the GNU Library Public License, version 2, hence", + " the version number 2.1.", + "]", + "", + "\t\t\t Preamble", + "", + " The licenses for most software are designed to take away your", + "freedom to share and change it. By contrast, the GNU General Public", + "Licenses are intended to guarantee your freedom to share and change", + "free software--to make sure the software is free for all its users.", + "", + " This license, the Lesser General Public License, applies to some", + "specially designated software packages--typically libraries--of the", + "Free Software Foundation and other authors who decide to use it. You", + "can use it too, but we suggest you first think carefully about whether", + "this license or the ordinary General Public License is the better", + "strategy to use in any particular case, based on the explanations below.", + "", + " When we speak of free software, we are referring to freedom of use,", + "not price. Our General Public Licenses are designed to make sure that", + "you have the freedom to distribute copies of free software (and charge", + "for this service if you wish); that you receive source code or can get", + "it if you want it; that you can change the software and use pieces of", + "it in new free programs; and that you are informed that you can do", + "these things.", + "", + " To protect your rights, we need to make restrictions that forbid", + "distributors to deny you these rights or to ask you to surrender these", + "rights. These restrictions translate to certain responsibilities for", + "you if you distribute copies of the library or if you modify it.", + "", + " For example, if you distribute copies of the library, whether gratis", + "or for a fee, you must give the recipients all the rights that we gave", + "you. You must make sure that they, too, receive or can get the source", + "code. If you link other code with the library, you must provide", + "complete object files to the recipients, so that they can relink them", + "with the library after making changes to the library and recompiling", + "it. And you must show them these terms so they know their rights.", + "", + " We protect your rights with a two-step method: (1) we copyright the", + "library, and (2) we offer you this license, which gives you legal", + "permission to copy, distribute and/or modify the library.", + "", + " To protect each distributor, we want to make it very clear that", + "there is no warranty for the free library. Also, if the library is", + "modified by someone else and passed on, the recipients should know", + "that what they have is not the original version, so that the original", + "author's reputation will not be affected by problems that might be", + "introduced by others.", + "", + " Finally, software patents pose a constant threat to the existence of", + "any free program. We wish to make sure that a company cannot", + "effectively restrict the users of a free program by obtaining a", + "restrictive license from a patent holder. Therefore, we insist that", + "any patent license obtained for a version of the library must be", + "consistent with the full freedom of use specified in this license.", + "", + " Most GNU software, including some libraries, is covered by the", + "ordinary GNU General Public License. This license, the GNU Lesser", + "General Public License, applies to certain designated libraries, and", + "is quite different from the ordinary General Public License. We use", + "this license for certain libraries in order to permit linking those", + "libraries into non-free programs.", + "", + " When a program is linked with a library, whether statically or using", + "a shared library, the combination of the two is legally speaking a", + "combined work, a derivative of the original library. The ordinary", + "General Public License therefore permits such linking only if the", + "entire combination fits its criteria of freedom. The Lesser General", + "Public License permits more lax criteria for linking other code with", + "the library.", + "", + " We call this license the \"Lesser\" General Public License because it", + "does Less to protect the user's freedom than the ordinary General", + "Public License. It also provides other free software developers Less", + "of an advantage over competing non-free programs. These disadvantages", + "are the reason we use the ordinary General Public License for many", + "libraries. However, the Lesser license provides advantages in certain", + "special circumstances.", + "", + " For example, on rare occasions, there may be a special need to", + "encourage the widest possible use of a certain library, so that it becomes", + "a de-facto standard. To achieve this, non-free programs must be", + "allowed to use the library. A more frequent case is that a free", + "library does the same job as widely used non-free libraries. In this", + "case, there is little to gain by limiting the free library to free", + "software only, so we use the Lesser General Public License.", + "", + " In other cases, permission to use a particular library in non-free", + "programs enables a greater number of people to use a large body of", + "free software. For example, permission to use the GNU C Library in", + "non-free programs enables many more people to use the whole GNU", + "operating system, as well as its variant, the GNU/Linux operating", + "system.", + "", + " Although the Lesser General Public License is Less protective of the", + "users' freedom, it does ensure that the user of a program that is", + "linked with the Library has the freedom and the wherewithal to run", + "that program using a modified version of the Library.", + "", + " The precise terms and conditions for copying, distribution and", + "modification follow. Pay close attention to the difference between a", + "\"work based on the library\" and a \"work that uses the library\". The", + "former contains code derived from the library, whereas the latter must", + "be combined with the library in order to run.", + "", + "\t\t GNU LESSER GENERAL PUBLIC LICENSE", + " TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION", + "", + " 0. This License Agreement applies to any software library or other", + "program which contains a notice placed by the copyright holder or", + "other authorized party saying it may be distributed under the terms of", + "this Lesser General Public License (also called \"this License\").", + "Each licensee is addressed as \"you\".", + "", + " A \"library\" means a collection of software functions and/or data", + "prepared so as to be conveniently linked with application programs", + "(which use some of those functions and data) to form executables.", + "", + " The \"Library\", below, refers to any such software library or work", + "which has been distributed under these terms. A \"work based on the", + "Library\" means either the Library or any derivative work under", + "copyright law: that is to say, a work containing the Library or a", + "portion of it, either verbatim or with modifications and/or translated", + "straightforwardly into another language. (Hereinafter, translation is", + "included without limitation in the term \"modification\".)", + "", + " \"Source code\" for a work means the preferred form of the work for", + "making modifications to it. For a library, complete source code means", + "all the source code for all modules it contains, plus any associated", + "interface definition files, plus the scripts used to control compilation", + "and installation of the library.", + "", + " Activities other than copying, distribution and modification are not", + "covered by this License; they are outside its scope. The act of", + "running a program using the Library is not restricted, and output from", + "such a program is covered only if its contents constitute a work based", + "on the Library (independent of the use of the Library in a tool for", + "writing it). Whether that is true depends on what the Library does", + "and what the program that uses the Library does.", + "", + " 1. You may copy and distribute verbatim copies of the Library's", + "complete source code as you receive it, in any medium, provided that", + "you conspicuously and appropriately publish on each copy an", + "appropriate copyright notice and disclaimer of warranty; keep intact", + "all the notices that refer to this License and to the absence of any", + "warranty; and distribute a copy of this License along with the", + "Library.", + "", + " You may charge a fee for the physical act of transferring a copy,", + "and you may at your option offer warranty protection in exchange for a", + "fee.", + "", + " 2. You may modify your copy or copies of the Library or any portion", + "of it, thus forming a work based on the Library, and copy and", + "distribute such modifications or work under the terms of Section 1", + "above, provided that you also meet all of these conditions:", + "", + " a) The modified work must itself be a software library.", + "", + " b) You must cause the files modified to carry prominent notices", + " stating that you changed the files and the date of any change.", + "", + " c) You must cause the whole of the work to be licensed at no", + " charge to all third parties under the terms of this License.", + "", + " d) If a facility in the modified Library refers to a function or a", + " table of data to be supplied by an application program that uses", + " the facility, other than as an argument passed when the facility", + " is invoked, then you must make a good faith effort to ensure that,", + " in the event an application does not supply such function or", + " table, the facility still operates, and performs whatever part of", + " its purpose remains meaningful.", + "", + " (For example, a function in a library to compute square roots has", + " a purpose that is entirely well-defined independent of the", + " application. Therefore, Subsection 2d requires that any", + " application-supplied function or table used by this function must", + " be optional: if the application does not supply it, the square", + " root function must still compute square roots.)", + "", + "These requirements apply to the modified work as a whole. If", + "identifiable sections of that work are not derived from the Library,", + "and can be reasonably considered independent and separate works in", + "themselves, then this License, and its terms, do not apply to those", + "sections when you distribute them as separate works. But when you", + "distribute the same sections as part of a whole which is a work based", + "on the Library, the distribution of the whole must be on the terms of", + "this License, whose permissions for other licensees extend to the", + "entire whole, and thus to each and every part regardless of who wrote", + "it.", + "", + "Thus, it is not the intent of this section to claim rights or contest", + "your rights to work written entirely by you; rather, the intent is to", + "exercise the right to control the distribution of derivative or", + "collective works based on the Library.", + "", + "In addition, mere aggregation of another work not based on the Library", + "with the Library (or with a work based on the Library) on a volume of", + "a storage or distribution medium does not bring the other work under", + "the scope of this License.", + "", + " 3. You may opt to apply the terms of the ordinary GNU General Public", + "License instead of this License to a given copy of the Library. To do", + "this, you must alter all the notices that refer to this License, so", + "that they refer to the ordinary GNU General Public License, version 2,", + "instead of to this License. (If a newer version than version 2 of the", + "ordinary GNU General Public License has appeared, then you can specify", + "that version instead if you wish.) Do not make any other change in", + "these notices.", + "", + " Once this change is made in a given copy, it is irreversible for", + "that copy, so the ordinary GNU General Public License applies to all", + "subsequent copies and derivative works made from that copy.", + "", + " This option is useful when you wish to copy part of the code of", + "the Library into a program that is not a library.", + "", + " 4. You may copy and distribute the Library (or a portion or", + "derivative of it, under Section 2) in object code or executable form", + "under the terms of Sections 1 and 2 above provided that you accompany", + "it with the complete corresponding machine-readable source code, which", + "must be distributed under the terms of Sections 1 and 2 above on a", + "medium customarily used for software interchange.", + "", + " If distribution of object code is made by offering access to copy", + "from a designated place, then offering equivalent access to copy the", + "source code from the same place satisfies the requirement to", + "distribute the source code, even though third parties are not", + "compelled to copy the source along with the object code.", + "", + " 5. A program that contains no derivative of any portion of the", + "Library, but is designed to work with the Library by being compiled or", + "linked with it, is called a \"work that uses the Library\". Such a", + "work, in isolation, is not a derivative work of the Library, and", + "therefore falls outside the scope of this License.", + "", + " However, linking a \"work that uses the Library\" with the Library", + "creates an executable that is a derivative of the Library (because it", + "contains portions of the Library), rather than a \"work that uses the", + "library\". The executable is therefore covered by this License.", + "Section 6 states terms for distribution of such executables.", + "", + " When a \"work that uses the Library\" uses material from a header file", + "that is part of the Library, the object code for the work may be a", + "derivative work of the Library even though the source code is not.", + "Whether this is true is especially significant if the work can be", + "linked without the Library, or if the work is itself a library. The", + "threshold for this to be true is not precisely defined by law.", + "", + " If such an object file uses only numerical parameters, data", + "structure layouts and accessors, and small macros and small inline", + "functions (ten lines or less in length), then the use of the object", + "file is unrestricted, regardless of whether it is legally a derivative", + "work. (Executables containing this object code plus portions of the", + "Library will still fall under Section 6.)", + "", + " Otherwise, if the work is a derivative of the Library, you may", + "distribute the object code for the work under the terms of Section 6.", + "Any executables containing that work also fall under Section 6,", + "whether or not they are linked directly with the Library itself.", + "", + " 6. As an exception to the Sections above, you may also combine or", + "link a \"work that uses the Library\" with the Library to produce a", + "work containing portions of the Library, and distribute that work", + "under terms of your choice, provided that the terms permit", + "modification of the work for the customer's own use and reverse", + "engineering for debugging such modifications.", + "", + " You must give prominent notice with each copy of the work that the", + "Library is used in it and that the Library and its use are covered by", + "this License. You must supply a copy of this License. If the work", + "during execution displays copyright notices, you must include the", + "copyright notice for the Library among them, as well as a reference", + "directing the user to the copy of this License. Also, you must do one", + "of these things:", + "", + " a) Accompany the work with the complete corresponding", + " machine-readable source code for the Library including whatever", + " changes were used in the work (which must be distributed under", + " Sections 1 and 2 above); and, if the work is an executable linked", + " with the Library, with the complete machine-readable \"work that", + " uses the Library\", as object code and/or source code, so that the", + " user can modify the Library and then relink to produce a modified", + " executable containing the modified Library. (It is understood", + " that the user who changes the contents of definitions files in the", + " Library will not necessarily be able to recompile the application", + " to use the modified definitions.)", + "", + " b) Use a suitable shared library mechanism for linking with the", + " Library. A suitable mechanism is one that (1) uses at run time a", + " copy of the library already present on the user's computer system,", + " rather than copying library functions into the executable, and (2)", + " will operate properly with a modified version of the library, if", + " the user installs one, as long as the modified version is", + " interface-compatible with the version that the work was made with.", + "", + " c) Accompany the work with a written offer, valid for at", + " least three years, to give the same user the materials", + " specified in Subsection 6a, above, for a charge no more", + " than the cost of performing this distribution.", + "", + " d) If distribution of the work is made by offering access to copy", + " from a designated place, offer equivalent access to copy the above", + " specified materials from the same place.", + "", + " e) Verify that the user has already received a copy of these", + " materials or that you have already sent this user a copy.", + "", + " For an executable, the required form of the \"work that uses the", + "Library\" must include any data and utility programs needed for", + "reproducing the executable from it. However, as a special exception,", + "the materials to be distributed need not include anything that is", + "normally distributed (in either source or binary form) with the major", + "components (compiler, kernel, and so on) of the operating system on", + "which the executable runs, unless that component itself accompanies", + "the executable.", + "", + " It may happen that this requirement contradicts the license", + "restrictions of other proprietary libraries that do not normally", + "accompany the operating system. Such a contradiction means you cannot", + "use both them and the Library together in an executable that you", + "distribute.", + "", + " 7. You may place library facilities that are a work based on the", + "Library side-by-side in a single library together with other library", + "facilities not covered by this License, and distribute such a combined", + "library, provided that the separate distribution of the work based on", + "the Library and of the other library facilities is otherwise", + "permitted, and provided that you do these two things:", + "", + " a) Accompany the combined library with a copy of the same work", + " based on the Library, uncombined with any other library", + " facilities. This must be distributed under the terms of the", + " Sections above.", + "", + " b) Give prominent notice with the combined library of the fact", + " that part of it is a work based on the Library, and explaining", + " where to find the accompanying uncombined form of the same work.", + "", + " 8. You may not copy, modify, sublicense, link with, or distribute", + "the Library except as expressly provided under this License. Any", + "attempt otherwise to copy, modify, sublicense, link with, or", + "distribute the Library is void, and will automatically terminate your", + "rights under this License. However, parties who have received copies,", + "or rights, from you under this License will not have their licenses", + "terminated so long as such parties remain in full compliance.", + "", + " 9. You are not required to accept this License, since you have not", + "signed it. However, nothing else grants you permission to modify or", + "distribute the Library or its derivative works. These actions are", + "prohibited by law if you do not accept this License. Therefore, by", + "modifying or distributing the Library (or any work based on the", + "Library), you indicate your acceptance of this License to do so, and", + "all its terms and conditions for copying, distributing or modifying", + "the Library or works based on it.", + "", + " 10. Each time you redistribute the Library (or any work based on the", + "Library), the recipient automatically receives a license from the", + "original licensor to copy, distribute, link with or modify the Library", + "subject to these terms and conditions. You may not impose any further", + "restrictions on the recipients' exercise of the rights granted herein.", + "You are not responsible for enforcing compliance by third parties with", + "this License.", + "", + " 11. If, as a consequence of a court judgment or allegation of patent", + "infringement or for any other reason (not limited to patent issues),", + "conditions are imposed on you (whether by court order, agreement or", + "otherwise) that contradict the conditions of this License, they do not", + "excuse you from the conditions of this License. If you cannot", + "distribute so as to satisfy simultaneously your obligations under this", + "License and any other pertinent obligations, then as a consequence you", + "may not distribute the Library at all. For example, if a patent", + "license would not permit royalty-free redistribution of the Library by", + "all those who receive copies directly or indirectly through you, then", + "the only way you could satisfy both it and this License would be to", + "refrain entirely from distribution of the Library.", + "", + "If any portion of this section is held invalid or unenforceable under any", + "particular circumstance, the balance of the section is intended to apply,", + "and the section as a whole is intended to apply in other circumstances.", + "", + "It is not the purpose of this section to induce you to infringe any", + "patents or other property right claims or to contest validity of any", + "such claims; this section has the sole purpose of protecting the", + "integrity of the free software distribution system which is", + "implemented by public license practices. Many people have made", + "generous contributions to the wide range of software distributed", + "through that system in reliance on consistent application of that", + "system; it is up to the author/donor to decide if he or she is willing", + "to distribute software through any other system and a licensee cannot", + "impose that choice.", + "", + "This section is intended to make thoroughly clear what is believed to", + "be a consequence of the rest of this License.", + "", + " 12. If the distribution and/or use of the Library is restricted in", + "certain countries either by patents or by copyrighted interfaces, the", + "original copyright holder who places the Library under this License may add", + "an explicit geographical distribution limitation excluding those countries,", + "so that distribution is permitted only in or among countries not thus", + "excluded. In such case, this License incorporates the limitation as if", + "written in the body of this License.", + "", + " 13. The Free Software Foundation may publish revised and/or new", + "versions of the Lesser General Public License from time to time.", + "Such new versions will be similar in spirit to the present version,", + "but may differ in detail to address new problems or concerns.", + "", + "Each version is given a distinguishing version number. If the Library", + "specifies a version number of this License which applies to it and", + "\"any later version\", you have the option of following the terms and", + "conditions either of that version or of any later version published by", + "the Free Software Foundation. If the Library does not specify a", + "license version number, you may choose any version ever published by", + "the Free Software Foundation.", + "", + " 14. If you wish to incorporate parts of the Library into other free", + "programs whose distribution conditions are incompatible with these,", + "write to the author to ask for permission. For software which is", + "copyrighted by the Free Software Foundation, write to the Free", + "Software Foundation; we sometimes make exceptions for this. Our", + "decision will be guided by the two goals of preserving the free status", + "of all derivatives of our free software and of promoting the sharing", + "and reuse of software generally.", + "", + "\t\t\t NO WARRANTY", + "", + " 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO", + "WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.", + "EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR", + "OTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY", + "KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE", + "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR", + "PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE", + "LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME", + "THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.", + "", + " 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN", + "WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY", + "AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU", + "FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR", + "CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE", + "LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING", + "RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A", + "FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF", + "SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH", + "DAMAGES.", + "", + "\t\t END OF TERMS AND CONDITIONS", + "", + " How to Apply These Terms to Your New Libraries", + "", + " If you develop a new library, and you want it to be of the greatest", + "possible use to the public, we recommend making it free software that", + "everyone can redistribute and change. You can do so by permitting", + "redistribution under these terms (or, alternatively, under the terms of the", + "ordinary General Public License).", + "", + " To apply these terms, attach the following notices to the library. It is", + "safest to attach them to the start of each source file to most effectively", + "convey the exclusion of warranty; and each file should have at least the", + "\"copyright\" line and a pointer to where the full notice is found.", + "", + " ", + " Copyright (C) ", + "", + " This library is free software; you can redistribute it and/or", + " modify it under the terms of the GNU Lesser General Public", + " License as published by the Free Software Foundation; either", + " version 2.1 of the License, or (at your option) any later version.", + "", + " This library is distributed in the hope that it will be useful,", + " but WITHOUT ANY WARRANTY; without even the implied warranty of", + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU", + " Lesser General Public License for more details.", + "", + " You should have received a copy of the GNU Lesser General Public", + " License along with this library; if not, write to the Free Software", + " Foundation, Inc.,", + "51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA", + "", + "Also add information on how to contact you by electronic and paper mail.", + "", + "You should also get your employer (if you work as a programmer) or your", + "school, if any, to sign a \"copyright disclaimer\" for the library, if", + "necessary. Here is a sample; alter the names:", + "", + " Yoyodyne, Inc., hereby disclaims all copyright interest in the", + " library `Frob' (a library for tweaking knobs) written by James Random Hacker.", + "", + " ,", + "1 April 1990", + " Ty Coon, President of Vice", + "", + "That's all there is to it!" + ] + }, + { + // Added here because the module `parse5` has a dependency to it. + // The module `parse5` is shipped via the `extension-editing` built-in extension. + // The module `parse5` does not want to remove it https://github.com/inikulin/parse5/issues/225 + "name": "@types/node", + "licenseDetail": [ + "This project is licensed under the MIT license.", + "Copyrights are respective of each contributor listed at the beginning of each definition file.", + "", + "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." + ] + }, + { + // We override the license that gets discovered at + // https://github.com/Microsoft/TypeScript/blob/master/LICENSE.txt + // because it does not contain a Copyright statement + "name": "typescript", + "licenseDetail": [ + "Copyright (c) Microsoft Corporation. All rights reserved.", + "", + "Apache License", + "", + "Version 2.0, January 2004", + "", + "http://www.apache.org/licenses/", + "", + "TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION", + "", + "1. Definitions.", + "", + "\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.", + "", + "\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.", + "", + "\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.", + "", + "\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.", + "", + "\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.", + "", + "\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.", + "", + "\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).", + "", + "\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.", + "", + "\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"", + "", + "\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.", + "", + "2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.", + "", + "3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.", + "", + "4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:", + "", + "You must give any other recipients of the Work or Derivative Works a copy of this License; and", + "", + "You must cause any modified files to carry prominent notices stating that You changed the files; and", + "", + "You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and", + "", + "If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.", + "", + "5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.", + "", + "6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.", + "", + "7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.", + "", + "8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.", + "", + "9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.", + "", + "END OF TERMS AND CONDITIONS" + ] + }, + { + // This module comes in from https://github.com/Microsoft/vscode-node-debug2/blob/master/package-lock.json + "name": "@types/source-map", + "licenseDetail": [ + "This project is licensed under the MIT license.", + "Copyrights are respective of each contributor listed at the beginning of each definition file.", + "", + "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." + ] + }, + { + "name": "tunnel-agent", + "licenseDetail": [ + "Copyright (c) tunnel-agent authors", + "", + "Apache License", + "", + "Version 2.0, January 2004", + "", + "http://www.apache.org/licenses/", + "", + "TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION", + "", + "1. Definitions.", + "", + "\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.", + "", + "\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.", + "", + "\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.", + "", + "\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.", + "", + "\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.", + "", + "\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.", + "", + "\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).", + "", + "\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.", + "", + "\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"", + "", + "\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.", + "", + "2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.", + "", + "3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.", + "", + "4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:", + "", + "You must give any other recipients of the Work or Derivative Works a copy of this License; and", + "", + "You must cause any modified files to carry prominent notices stating that You changed the files; and", + "", + "You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and", + "", + "If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.", + "", + "5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.", + "", + "6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.", + "", + "7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.", + "", + "8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.", + "", + "9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.", + "", + "END OF TERMS AND CONDITIONS" + ] + }, + { + // Waiting for https://github.com/segmentio/noop-logger/issues/2 + "name": "noop-logger", + "licenseDetail": [ + "This project is licensed under the MIT license.", + "Copyrights are respective of each contributor listed at the beginning of each definition file.", + "", + "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." + ] + }, + { + "name": "xterm-addon-search", + "licenseDetail": [ + "Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)", + "", + "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." + ] + }, + { + "name": "xterm-addon-web-links", + "licenseDetail": [ + "Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." + ] + } +] diff --git a/cgmanifest.json b/cgmanifest.json index 42a65648e59..b8c4f3bfefc 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "c1b5a1cfc8a14a337540193daecfa5d0f50dd7bb" + "commitHash": "5d67ec3da5376a5058990e8a9557bc9124ad59a8" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "4.2.4" + "version": "4.2.5" }, { "component": { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index df9de2ad974..30348608dd0 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -339,7 +339,7 @@ export class Git { } async clone(url: string, parentPath: string, cancellationToken?: CancellationToken): Promise { - let baseFolderName = decodeURI(url).replace(/^.*\//, '').replace(/\.git$/, '') || 'repository'; + let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*\//, '').replace(/\.git$/, '') || 'repository'; let folderName = baseFolderName; let folderPath = path.join(parentPath, folderName); let count = 1; diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 2c9085b6820..a04dc33c8be 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -233,7 +233,7 @@ export class Model { } const dotGit = await this.git.getRepositoryDotGit(repositoryRoot); - const repository = new Repository(this.git.open(repositoryRoot, dotGit), this.globalState); + const repository = new Repository(this.git.open(repositoryRoot, dotGit), this.globalState, this.outputChannel); this.open(repository); } catch (err) { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index a6ac57bc691..a581f9f5fd4 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType } from 'vscode'; +import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env } from 'vscode'; import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git'; -import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, toDisposable } from './util'; +import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable, watch, IFileWatcher } from './util'; import { memoize, throttle, debounce } from './decorators'; import { toGitUri } from './uri'; import { AutoFetcher } from './autofetch'; @@ -447,6 +447,91 @@ class ProgressManager { } } +class FileEventLogger { + + private eventDisposable: IDisposable = EmptyDisposable; + private logLevelDisposable: IDisposable = EmptyDisposable; + + constructor( + private onWorkspaceWorkingTreeFileChange: Event, + private onDotGitFileChange: Event, + private outputChannel: OutputChannel + ) { + this.logLevelDisposable = env.onDidChangeLogLevel(this.onDidChangeLogLevel, this); + this.onDidChangeLogLevel(env.logLevel); + } + + private onDidChangeLogLevel(level: LogLevel): void { + this.eventDisposable.dispose(); + + if (level > LogLevel.Debug) { + return; + } + + this.eventDisposable = combinedDisposable([ + this.onWorkspaceWorkingTreeFileChange(uri => this.outputChannel.appendLine(`[debug] [wt] Change: ${uri.fsPath}`)), + this.onDotGitFileChange(uri => this.outputChannel.appendLine(`[debug] [.git] Change: ${uri.fsPath}`)) + ]); + } + + dispose(): void { + this.eventDisposable.dispose(); + this.logLevelDisposable.dispose(); + } +} + +class DotGitWatcher implements IFileWatcher { + + readonly event: Event; + + private emitter = new EventEmitter(); + private transientDisposables: IDisposable[] = []; + private disposables: IDisposable[] = []; + + constructor( + private repository: Repository, + private outputChannel: OutputChannel + ) { + const rootWatcher = watch(repository.dotGit); + this.disposables.push(rootWatcher); + + const filteredRootWatcher = filterEvent(rootWatcher.event, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path)); + this.event = anyEvent(filteredRootWatcher, this.emitter.event); + + repository.onDidRunGitStatus(this.updateTransientWatchers, this, this.disposables); + this.updateTransientWatchers(); + } + + private updateTransientWatchers() { + this.transientDisposables = dispose(this.transientDisposables); + + if (!this.repository.HEAD || !this.repository.HEAD.upstream) { + return; + } + + this.transientDisposables = dispose(this.transientDisposables); + + const { name, remote } = this.repository.HEAD.upstream; + const upstreamPath = path.join(this.repository.dotGit, 'refs', 'remotes', remote, name); + + try { + const upstreamWatcher = watch(upstreamPath); + this.transientDisposables.push(upstreamWatcher); + upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables); + } catch (err) { + if (env.logLevel <= LogLevel.Info) { + this.outputChannel.appendLine(`Failed to watch ref '${upstreamPath}'. Ref is most likely packed.`); + } + } + } + + dispose() { + this.emitter.dispose(); + this.transientDisposables = dispose(this.transientDisposables); + this.disposables = dispose(this.disposables); + } +} + export class Repository implements Disposable { private _onDidChangeRepository = new EventEmitter(); @@ -544,37 +629,41 @@ export class Repository implements Disposable { return this.repository.root; } + get dotGit(): string { + return this.repository.dotGit; + } + private isRepositoryHuge = false; private didWarnAboutLimit = false; private isFreshRepository: boolean | undefined = undefined; + private disposables: Disposable[] = []; constructor( private readonly repository: BaseRepository, - globalState: Memento + globalState: Memento, + outputChannel: OutputChannel ) { const workspaceWatcher = workspace.createFileSystemWatcher('**'); this.disposables.push(workspaceWatcher); - const onWorkspaceFileChanges = anyEvent(workspaceWatcher.onDidChange, workspaceWatcher.onDidCreate, workspaceWatcher.onDidDelete); - const onWorkspaceRepositoryFileChanges = filterEvent(onWorkspaceFileChanges, uri => isDescendant(repository.root, uri.fsPath)); - const onWorkspaceWorkingTreeFileChanges = filterEvent(onWorkspaceRepositoryFileChanges, uri => !/\/\.git($|\/)/.test(uri.path)); + const onWorkspaceFileChange = anyEvent(workspaceWatcher.onDidChange, workspaceWatcher.onDidCreate, workspaceWatcher.onDidDelete); + const onWorkspaceRepositoryFileChange = filterEvent(onWorkspaceFileChange, uri => isDescendant(repository.root, uri.fsPath)); + const onWorkspaceWorkingTreeFileChange = filterEvent(onWorkspaceRepositoryFileChange, uri => !/\/\.git($|\/)/.test(uri.path)); - const dotGitWatcher = fs.watch(repository.dotGit); - const onRepositoryFileEmitter = new EventEmitter(); - dotGitWatcher.on('change', (_, e) => onRepositoryFileEmitter.fire(Uri.file(path.join(repository.dotGit, e as string)))); - dotGitWatcher.on('error', err => console.error(err)); - this.disposables.push(toDisposable(() => dotGitWatcher.close())); - const onRelevantRepositoryChanges = filterEvent(onRepositoryFileEmitter.event, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path)); + const dotGitFileWatcher = new DotGitWatcher(this, outputChannel); + this.disposables.push(dotGitFileWatcher); // FS changes should trigger `git status`: // - any change inside the repository working tree // - any change whithin the first level of the `.git` folder, except the folder itself and `index.lock` - const onFSChange = anyEvent(onWorkspaceWorkingTreeFileChanges, onRelevantRepositoryChanges); - onFSChange(this.onFSChange, this, this.disposables); + const onFileChange = anyEvent(onWorkspaceWorkingTreeFileChange, dotGitFileWatcher.event); + onFileChange(this.onFileChange, this, this.disposables); // Relevate repository changes should trigger virtual document change events - onRelevantRepositoryChanges(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables); + dotGitFileWatcher.event(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables); + + this.disposables.push(new FileEventLogger(onWorkspaceWorkingTreeFileChange, dotGitFileWatcher.event, outputChannel)); const root = Uri.file(repository.root); this._sourceControl = scm.createSourceControl('git', 'Git', root); @@ -584,9 +673,9 @@ export class Repository implements Disposable { this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); this.disposables.push(this._sourceControl); - this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes")); - this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes")); - this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes")); + this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES")); + this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES")); + this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES")); const updateIndexGroupVisibility = () => { const config = workspace.getConfiguration('git', root); @@ -1454,7 +1543,7 @@ export class Repository implements Disposable { return result; } - private onFSChange(_uri: Uri): void { + private onFileChange(_uri: Uri): void { const config = workspace.getConfiguration('git'); const autorefresh = config.get('autorefresh'); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 7bf81adccd9..c4e93850619 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vscode'; -import { dirname, sep } from 'path'; +import { Event, EventEmitter, Uri } from 'vscode'; +import { dirname, sep, join } from 'path'; import { Readable } from 'stream'; import * as fs from 'fs'; import * as byline from 'byline'; @@ -343,4 +343,20 @@ export function pathEquals(a: string, b: string): boolean { } return a === b; -} \ No newline at end of file +} + +export interface IFileWatcher extends IDisposable { + readonly event: Event; +} + +export function watch(location: string): IFileWatcher { + const dotGitWatcher = fs.watch(location); + const onDotGitFileChangeEmitter = new EventEmitter(); + dotGitWatcher.on('change', (_, e) => onDotGitFileChangeEmitter.fire(Uri.file(join(location, e as string)))); + dotGitWatcher.on('error', err => console.error(err)); + + return new class implements IFileWatcher { + event = onDotGitFileChangeEmitter.event; + dispose() { dotGitWatcher.close(); } + }; +} diff --git a/extensions/json/package.json b/extensions/json/package.json index 9c8b0f84a2a..1606f6a3d64 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -24,7 +24,6 @@ ".bowerrc", ".jshintrc", ".jscsrc", - ".eslintrc", ".swcrc", ".webmanifest", ".js.map", @@ -49,7 +48,9 @@ "extensions": [ ".hintrc", ".babelrc", - ".jsonc" + ".jsonc", + ".eslintrc", + ".eslintrc.json" ], "configuration": "./language-configuration.json" } diff --git a/extensions/markdown-language-features/media/index.js b/extensions/markdown-language-features/media/index.js index 8a4eea9541d..00afdf196af 100644 --- a/extensions/markdown-language-features/media/index.js +++ b/extensions/markdown-language-features/media/index.js @@ -741,8 +741,8 @@ document.addEventListener('click', event => { if (node.getAttribute('href').startsWith('#')) { break; } - if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:')) { - const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').split('#'); + if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:') || node.href.startsWith(settings.webviewResourceRoot)) { + const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').replace(new RegExp(`^${escapeRegExp(settings.webviewResourceRoot)}`)).split('#'); messaging.postMessage('clickLink', { path, fragment }); event.preventDefault(); event.stopPropagation(); @@ -768,6 +768,9 @@ if (settings.scrollEditorWithPreview) { } }, 50)); } +function escapeRegExp(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); +} /***/ }), @@ -983,4 +986,4 @@ exports.getSettings = getSettings; /***/ }) /******/ }); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vbm9kZV9tb2R1bGVzL2xvZGFzaC50aHJvdHRsZS9pbmRleC5qcyIsIndlYnBhY2s6Ly8vKHdlYnBhY2spL2J1aWxkaW4vZ2xvYmFsLmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2FjdGl2ZUxpbmVNYXJrZXIudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvZXZlbnRzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2luZGV4LnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zY3JvbGwtc3luYy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQSx5REFBaUQsY0FBYztBQUMvRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBMkIsMEJBQTBCLEVBQUU7QUFDdkQseUNBQWlDLGVBQWU7QUFDaEQ7QUFDQTtBQUNBOztBQUVBO0FBQ0EsOERBQXNELCtEQUErRDs7QUFFckg7QUFDQTs7O0FBR0E7QUFDQTs7Ozs7Ozs7Ozs7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsT0FBTztBQUNsQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBLDhDQUE4QyxrQkFBa0I7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQsb0JBQW9CO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsRUFBRTtBQUNiLGFBQWEsUUFBUTtBQUNyQjtBQUNBO0FBQ0Esb0JBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxFQUFFO0FBQ2IsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOzs7Ozs7Ozs7Ozs7O0FDdGJBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQ7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsNENBQTRDOztBQUU1Qzs7Ozs7Ozs7Ozs7Ozs7O0FDbkJBOzs7Z0dBR2dHO0FBQ2hHLCtGQUF5RDtBQUV6RCxNQUFhLGdCQUFnQjtJQUc1Qiw4QkFBOEIsQ0FBQyxJQUFZO1FBQzFDLE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxzQ0FBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNwRCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVELE9BQU8sQ0FBQyxNQUErQjtRQUN0QyxJQUFJLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3pDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQyxJQUFJLENBQUMsUUFBUSxHQUFHLE1BQU0sQ0FBQztJQUN4QixDQUFDO0lBRUQsb0JBQW9CLENBQUMsT0FBZ0M7UUFDcEQsSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNiLE9BQU87U0FDUDtRQUNELE9BQU8sQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsdUJBQXVCLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUVELGtCQUFrQixDQUFDLE9BQWdDO1FBQ2xELElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDYixPQUFPO1NBQ1A7UUFDRCxPQUFPLENBQUMsU0FBUyxJQUFJLG1CQUFtQixDQUFDO0lBQzFDLENBQUM7Q0FDRDtBQTNCRCw0Q0EyQkM7Ozs7Ozs7Ozs7Ozs7O0FDakNEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixrQkFBa0IsQ0FBQyxDQUFhO0lBQy9DLElBQUksUUFBUSxDQUFDLFVBQVUsS0FBSyxTQUFTLElBQUksUUFBUSxDQUFDLFVBQW9CLEtBQUssZUFBZSxFQUFFO1FBQzNGLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNqRDtTQUFNO1FBQ04sQ0FBQyxFQUFFLENBQUM7S0FDSjtBQUNGLENBQUM7QUFORCxnREFNQzs7Ozs7Ozs7Ozs7Ozs7QUNYRDs7O2dHQUdnRzs7QUFFaEcsOEdBQXNEO0FBQ3RELGdGQUE4QztBQUM5Qyx5RkFBb0Q7QUFDcEQsK0ZBQTJGO0FBQzNGLHNGQUFrRDtBQUNsRCx1R0FBNkM7QUFJN0MsSUFBSSxjQUFjLEdBQUcsSUFBSSxDQUFDO0FBQzFCLE1BQU0sTUFBTSxHQUFHLElBQUksbUNBQWdCLEVBQUUsQ0FBQztBQUN0QyxNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7QUFFL0IsTUFBTSxNQUFNLEdBQUcsZ0JBQWdCLEVBQUUsQ0FBQztBQUVsQyxvQkFBb0I7QUFDcEIsSUFBSSxLQUFLLEdBQUcsa0JBQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUNsQyxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBRXZCLE1BQU0sU0FBUyxHQUFHLGlDQUFxQixDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBRWhELE1BQU0sQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3ZDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLENBQUM7QUFFaEQsTUFBTSxDQUFDLE1BQU0sR0FBRyxHQUFHLEVBQUU7SUFDcEIsZ0JBQWdCLEVBQUUsQ0FBQztBQUNwQixDQUFDLENBQUM7QUFFRiwyQkFBa0IsQ0FBQyxHQUFHLEVBQUU7SUFDdkIsSUFBSSxRQUFRLENBQUMsdUJBQXVCLEVBQUU7UUFDckMsVUFBVSxDQUFDLEdBQUcsRUFBRTtZQUNmLE1BQU0sV0FBVyxHQUFHLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQztZQUNuQyxJQUFJLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxFQUFFO2dCQUN4QixjQUFjLEdBQUcsSUFBSSxDQUFDO2dCQUN0QixzQ0FBd0IsQ0FBQyxXQUFXLENBQUMsQ0FBQzthQUN0QztRQUNGLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNOO0FBQ0YsQ0FBQyxDQUFDLENBQUM7QUFFSCxNQUFNLFlBQVksR0FBRyxDQUFDLEdBQUcsRUFBRTtJQUMxQixNQUFNLFFBQVEsR0FBRyxRQUFRLENBQUMsQ0FBQyxJQUFZLEVBQUUsRUFBRTtRQUMxQyxjQUFjLEdBQUcsSUFBSSxDQUFDO1FBQ3RCLHNDQUF3QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2hDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUVQLE9BQU8sQ0FBQyxJQUFZLEVBQUUsUUFBYSxFQUFFLEVBQUU7UUFDdEMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUNqQixRQUFRLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztZQUNyQixRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDZjtJQUNGLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxFQUFFLENBQUM7QUFFTCxJQUFJLGdCQUFnQixHQUFHLFFBQVEsQ0FBQyxHQUFHLEVBQUU7SUFDcEMsTUFBTSxTQUFTLEdBQW9ELEVBQUUsQ0FBQztJQUN0RSxJQUFJLE1BQU0sR0FBRyxRQUFRLENBQUMsb0JBQW9CLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDbEQsSUFBSSxNQUFNLEVBQUU7UUFDWCxJQUFJLENBQUMsQ0FBQztRQUNOLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUNuQyxNQUFNLEdBQUcsR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFdEIsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUMsRUFBRTtnQkFDdEMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7YUFDaEM7WUFFRCxTQUFTLENBQUMsSUFBSSxDQUFDO2dCQUNkLEVBQUUsRUFBRSxHQUFHLENBQUMsRUFBRTtnQkFDVixNQUFNLEVBQUUsR0FBRyxDQUFDLE1BQU07Z0JBQ2xCLEtBQUssRUFBRSxHQUFHLENBQUMsS0FBSzthQUNoQixDQUFDLENBQUM7U0FDSDtRQUVELFNBQVMsQ0FBQyxXQUFXLENBQUMsaUJBQWlCLEVBQUUsU0FBUyxDQUFDLENBQUM7S0FDcEQ7QUFDRixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFFUCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxFQUFFLEdBQUcsRUFBRTtJQUN0QyxjQUFjLEdBQUcsSUFBSSxDQUFDO0lBQ3RCLGdCQUFnQixFQUFFLENBQUM7QUFDcEIsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBRVQsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxLQUFLLENBQUMsRUFBRTtJQUMxQyxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLFFBQVEsQ0FBQyxNQUFNLEVBQUU7UUFDMUMsT0FBTztLQUNQO0lBRUQsUUFBUSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRTtRQUN4QixLQUFLLGdDQUFnQztZQUNwQyxNQUFNLENBQUMsOEJBQThCLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUN2RCxNQUFNO1FBRVAsS0FBSyxZQUFZO1lBQ2hCLFlBQVksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUN4QyxNQUFNO0tBQ1A7QUFDRixDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFFVixRQUFRLENBQUMsZ0JBQWdCLENBQUMsVUFBVSxFQUFFLEtBQUssQ0FBQyxFQUFFO0lBQzdDLElBQUksQ0FBQyxRQUFRLENBQUMsMkJBQTJCLEVBQUU7UUFDMUMsT0FBTztLQUNQO0lBRUQseUJBQXlCO0lBQ3pCLEtBQUssSUFBSSxJQUFJLEdBQUcsS0FBSyxDQUFDLE1BQXFCLEVBQUUsSUFBSSxFQUFFLElBQUksR0FBRyxJQUFJLENBQUMsVUFBeUIsRUFBRTtRQUN6RixJQUFJLElBQUksQ0FBQyxPQUFPLEtBQUssR0FBRyxFQUFFO1lBQ3pCLE9BQU87U0FDUDtLQUNEO0lBRUQsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQztJQUMzQixNQUFNLElBQUksR0FBRyw4Q0FBZ0MsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUN0RCxJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRTtRQUM3QyxTQUFTLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztLQUM5RDtBQUNGLENBQUMsQ0FBQyxDQUFDO0FBRUgsUUFBUSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsRUFBRTtJQUMxQyxJQUFJLENBQUMsS0FBSyxFQUFFO1FBQ1gsT0FBTztLQUNQO0lBRUQsSUFBSSxJQUFJLEdBQVEsS0FBSyxDQUFDLE1BQU0sQ0FBQztJQUM3QixPQUFPLElBQUksRUFBRTtRQUNaLElBQUksSUFBSSxDQUFDLE9BQU8sSUFBSSxJQUFJLENBQUMsT0FBTyxLQUFLLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxFQUFFO1lBQ3RELElBQUksSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUU7Z0JBQzlDLE1BQU07YUFDTjtZQUNELElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsa0JBQWtCLENBQUMsRUFBRTtnQkFDaEYsTUFBTSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxnQ0FBZ0MsRUFBRSxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQzVGLFNBQVMsQ0FBQyxXQUFXLENBQUMsV0FBVyxFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7Z0JBQ3ZELEtBQUssQ0FBQyxjQUFjLEVBQUUsQ0FBQztnQkFDdkIsS0FBSyxDQUFDLGVBQWUsRUFBRSxDQUFDO2dCQUN4QixNQUFNO2FBQ047WUFDRCxNQUFNO1NBQ047UUFDRCxJQUFJLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQztLQUN2QjtBQUNGLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUVULElBQUksUUFBUSxDQUFDLHVCQUF1QixFQUFFO0lBQ3JDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLEdBQUcsRUFBRTtRQUMvQyxJQUFJLGNBQWMsRUFBRTtZQUNuQixjQUFjLEdBQUcsS0FBSyxDQUFDO1NBQ3ZCO2FBQU07WUFDTixNQUFNLElBQUksR0FBRyw4Q0FBZ0MsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDOUQsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUU7Z0JBQzdDLFNBQVMsQ0FBQyxXQUFXLENBQUMsWUFBWSxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztnQkFDOUMsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7Z0JBQ2xCLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7YUFDdkI7U0FDRDtJQUNGLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0NBQ1I7Ozs7Ozs7Ozs7Ozs7O0FDL0pEOzs7Z0dBR2dHOztBQUVoRyxzRkFBeUM7QUFTNUIsNkJBQXFCLEdBQUcsQ0FBQyxNQUFXLEVBQUUsRUFBRTtJQUNwRCxPQUFPLElBQUk7UUFDVixXQUFXLENBQUMsSUFBWSxFQUFFLElBQVk7WUFDckMsTUFBTSxDQUFDLFdBQVcsQ0FBQztnQkFDbEIsSUFBSTtnQkFDSixNQUFNLEVBQUUsc0JBQVcsRUFBRSxDQUFDLE1BQU07Z0JBQzVCLElBQUk7YUFDSixDQUFDLENBQUM7UUFDSixDQUFDO0tBQ0QsQ0FBQztBQUNILENBQUMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7QUN4QkY7OztnR0FHZ0c7O0FBRWhHLHNGQUF5QztBQUd6QyxTQUFTLEtBQUssQ0FBQyxHQUFXLEVBQUUsR0FBVyxFQUFFLEtBQWE7SUFDckQsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDO0FBQzVDLENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxJQUFZO0lBQzlCLE9BQU8sS0FBSyxDQUFDLENBQUMsRUFBRSxzQkFBVyxFQUFFLENBQUMsU0FBUyxHQUFHLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUNwRCxDQUFDO0FBUUQsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLEdBQUcsRUFBRTtJQUNqQyxJQUFJLFFBQTJCLENBQUM7SUFDaEMsT0FBTyxHQUFHLEVBQUU7UUFDWCxJQUFJLENBQUMsUUFBUSxFQUFFO1lBQ2QsUUFBUSxHQUFHLENBQUMsRUFBRSxPQUFPLEVBQUUsUUFBUSxDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNqRCxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxXQUFXLENBQUMsRUFBRTtnQkFDbkUsTUFBTSxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLFdBQVcsQ0FBRSxDQUFDO2dCQUNqRCxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO29CQUNqQixRQUFRLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLE9BQXNCLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztpQkFDekQ7YUFDRDtTQUNEO1FBQ0QsT0FBTyxRQUFRLENBQUM7SUFDakIsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUVMOzs7OztHQUtHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsVUFBa0I7SUFDMUQsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUMxQyxNQUFNLEtBQUssR0FBRyxtQkFBbUIsRUFBRSxDQUFDO0lBQ3BDLElBQUksUUFBUSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDaEMsS0FBSyxNQUFNLEtBQUssSUFBSSxLQUFLLEVBQUU7UUFDMUIsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLFVBQVUsRUFBRTtZQUM5QixPQUFPLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUM7U0FDNUM7YUFBTSxJQUFJLEtBQUssQ0FBQyxJQUFJLEdBQUcsVUFBVSxFQUFFO1lBQ25DLE9BQU8sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO1NBQ2pDO1FBQ0QsUUFBUSxHQUFHLEtBQUssQ0FBQztLQUNqQjtJQUNELE9BQU8sRUFBRSxRQUFRLEVBQUUsQ0FBQztBQUNyQixDQUFDO0FBYkQsNERBYUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLDJCQUEyQixDQUFDLE1BQWM7SUFDekQsTUFBTSxLQUFLLEdBQUcsbUJBQW1CLEVBQUUsQ0FBQztJQUNwQyxNQUFNLFFBQVEsR0FBRyxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQztJQUN6QyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNaLElBQUksRUFBRSxHQUFHLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQzFCLE9BQU8sRUFBRSxHQUFHLENBQUMsR0FBRyxFQUFFLEVBQUU7UUFDbkIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUN0QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDMUQsSUFBSSxNQUFNLENBQUMsR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksUUFBUSxFQUFFO1lBQzNDLEVBQUUsR0FBRyxHQUFHLENBQUM7U0FDVDthQUNJO1lBQ0osRUFBRSxHQUFHLEdBQUcsQ0FBQztTQUNUO0tBQ0Q7SUFDRCxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDNUIsTUFBTSxRQUFRLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO0lBQzNELElBQUksRUFBRSxJQUFJLENBQUMsSUFBSSxRQUFRLENBQUMsR0FBRyxHQUFHLFFBQVEsRUFBRTtRQUN2QyxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDNUIsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFDO0tBQ2hEO0lBQ0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBdEJELGtFQXNCQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsSUFBWTtJQUNwRCxJQUFJLENBQUMsc0JBQVcsRUFBRSxDQUFDLHVCQUF1QixFQUFFO1FBQzNDLE9BQU87S0FDUDtJQUVELElBQUksSUFBSSxJQUFJLENBQUMsRUFBRTtRQUNkLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNqQyxPQUFPO0tBQ1A7SUFFRCxNQUFNLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxHQUFHLHdCQUF3QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUU7UUFDZCxPQUFPO0tBQ1A7SUFDRCxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFDakIsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO0lBQ3RELE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUM7SUFDN0IsSUFBSSxJQUFJLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsSUFBSSxFQUFFO1FBQ3hDLDhEQUE4RDtRQUM5RCxNQUFNLGVBQWUsR0FBRyxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM3RSxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLFdBQVcsQ0FBQztRQUM3RSxRQUFRLEdBQUcsV0FBVyxHQUFHLGVBQWUsR0FBRyxhQUFhLENBQUM7S0FDekQ7U0FBTTtRQUNOLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDbEQsUUFBUSxHQUFHLFdBQVcsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsaUJBQWlCLENBQUMsQ0FBQztLQUMzRDtJQUNELE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsT0FBTyxHQUFHLFFBQVEsQ0FBQyxDQUFDLENBQUM7QUFDdkUsQ0FBQztBQTNCRCw0REEyQkM7QUFFRCxTQUFnQixnQ0FBZ0MsQ0FBQyxNQUFjO0lBQzlELE1BQU0sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEdBQUcsMkJBQTJCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDL0QsSUFBSSxRQUFRLEVBQUU7UUFDYixNQUFNLGNBQWMsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDaEUsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMxRSxJQUFJLElBQUksRUFBRTtZQUNULE1BQU0sdUJBQXVCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNySCxNQUFNLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxHQUFHLHVCQUF1QixHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDbkYsT0FBTyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDdkI7YUFDSTtZQUNKLE1BQU0scUJBQXFCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDM0UsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksR0FBRyxxQkFBcUIsQ0FBQztZQUNuRCxPQUFPLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN2QjtLQUNEO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDYixDQUFDO0FBakJELDRFQWlCQzs7Ozs7Ozs7Ozs7Ozs7QUN2SUQ7OztnR0FHZ0c7O0FBWWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFDLEdBQVc7SUFDbEMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0MiLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IFwiLi9wcmV2aWV3LXNyYy9pbmRleC50c1wiKTtcbiIsIi8qKlxuICogbG9kYXNoIChDdXN0b20gQnVpbGQpIDxodHRwczovL2xvZGFzaC5jb20vPlxuICogQnVpbGQ6IGBsb2Rhc2ggbW9kdWxhcml6ZSBleHBvcnRzPVwibnBtXCIgLW8gLi9gXG4gKiBDb3B5cmlnaHQgalF1ZXJ5IEZvdW5kYXRpb24gYW5kIG90aGVyIGNvbnRyaWJ1dG9ycyA8aHR0cHM6Ly9qcXVlcnkub3JnLz5cbiAqIFJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlIDxodHRwczovL2xvZGFzaC5jb20vbGljZW5zZT5cbiAqIEJhc2VkIG9uIFVuZGVyc2NvcmUuanMgMS44LjMgPGh0dHA6Ly91bmRlcnNjb3JlanMub3JnL0xJQ0VOU0U+XG4gKiBDb3B5cmlnaHQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbiAqL1xuXG4vKiogVXNlZCBhcyB0aGUgYFR5cGVFcnJvcmAgbWVzc2FnZSBmb3IgXCJGdW5jdGlvbnNcIiBtZXRob2RzLiAqL1xudmFyIEZVTkNfRVJST1JfVEVYVCA9ICdFeHBlY3RlZCBhIGZ1bmN0aW9uJztcblxuLyoqIFVzZWQgYXMgcmVmZXJlbmNlcyBmb3IgdmFyaW91cyBgTnVtYmVyYCBjb25zdGFudHMuICovXG52YXIgTkFOID0gMCAvIDA7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBzeW1ib2xUYWcgPSAnW29iamVjdCBTeW1ib2xdJztcblxuLyoqIFVzZWQgdG8gbWF0Y2ggbGVhZGluZyBhbmQgdHJhaWxpbmcgd2hpdGVzcGFjZS4gKi9cbnZhciByZVRyaW0gPSAvXlxccyt8XFxzKyQvZztcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJhZCBzaWduZWQgaGV4YWRlY2ltYWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmFkSGV4ID0gL15bLStdMHhbMC05YS1mXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBiaW5hcnkgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmluYXJ5ID0gL14wYlswMV0rJC9pO1xuXG4vKiogVXNlZCB0byBkZXRlY3Qgb2N0YWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzT2N0YWwgPSAvXjBvWzAtN10rJC9pO1xuXG4vKiogQnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMgd2l0aG91dCBhIGRlcGVuZGVuY3kgb24gYHJvb3RgLiAqL1xudmFyIGZyZWVQYXJzZUludCA9IHBhcnNlSW50O1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYGdsb2JhbGAgZnJvbSBOb2RlLmpzLiAqL1xudmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbCAmJiBnbG9iYWwuT2JqZWN0ID09PSBPYmplY3QgJiYgZ2xvYmFsO1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYHNlbGZgLiAqL1xudmFyIGZyZWVTZWxmID0gdHlwZW9mIHNlbGYgPT0gJ29iamVjdCcgJiYgc2VsZiAmJiBzZWxmLk9iamVjdCA9PT0gT2JqZWN0ICYmIHNlbGY7XG5cbi8qKiBVc2VkIGFzIGEgcmVmZXJlbmNlIHRvIHRoZSBnbG9iYWwgb2JqZWN0LiAqL1xudmFyIHJvb3QgPSBmcmVlR2xvYmFsIHx8IGZyZWVTZWxmIHx8IEZ1bmN0aW9uKCdyZXR1cm4gdGhpcycpKCk7XG5cbi8qKiBVc2VkIGZvciBidWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZVxuICogW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmplY3RUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXgsXG4gICAgbmF0aXZlTWluID0gTWF0aC5taW47XG5cbi8qKlxuICogR2V0cyB0aGUgdGltZXN0YW1wIG9mIHRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRoYXQgaGF2ZSBlbGFwc2VkIHNpbmNlXG4gKiB0aGUgVW5peCBlcG9jaCAoMSBKYW51YXJ5IDE5NzAgMDA6MDA6MDAgVVRDKS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDIuNC4wXG4gKiBAY2F0ZWdvcnkgRGF0ZVxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgdGltZXN0YW1wLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmRlZmVyKGZ1bmN0aW9uKHN0YW1wKSB7XG4gKiAgIGNvbnNvbGUubG9nKF8ubm93KCkgLSBzdGFtcCk7XG4gKiB9LCBfLm5vdygpKTtcbiAqIC8vID0+IExvZ3MgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgaXQgdG9vayBmb3IgdGhlIGRlZmVycmVkIGludm9jYXRpb24uXG4gKi9cbnZhciBub3cgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHJvb3QuRGF0ZS5ub3coKTtcbn07XG5cbi8qKlxuICogQ3JlYXRlcyBhIGRlYm91bmNlZCBmdW5jdGlvbiB0aGF0IGRlbGF5cyBpbnZva2luZyBgZnVuY2AgdW50aWwgYWZ0ZXIgYHdhaXRgXG4gKiBtaWxsaXNlY29uZHMgaGF2ZSBlbGFwc2VkIHNpbmNlIHRoZSBsYXN0IHRpbWUgdGhlIGRlYm91bmNlZCBmdW5jdGlvbiB3YXNcbiAqIGludm9rZWQuIFRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgIG1ldGhvZCB0byBjYW5jZWxcbiAqIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLlxuICogUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2Agc2hvdWxkIGJlIGludm9rZWQgb24gdGhlXG4gKiBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGAgdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkXG4gKiB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50XG4gKiBjYWxscyB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHJldHVybiB0aGUgcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYFxuICogaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIGRlYm91bmNlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy5kZWJvdW5jZWAgYW5kIGBfLnRocm90dGxlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGRlYm91bmNlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIGRlbGF5LlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9ZmFsc2VdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgbGVhZGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHBhcmFtIHtudW1iZXJ9IFtvcHRpb25zLm1heFdhaXRdXG4gKiAgVGhlIG1heGltdW0gdGltZSBgZnVuY2AgaXMgYWxsb3dlZCB0byBiZSBkZWxheWVkIGJlZm9yZSBpdCdzIGludm9rZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGRlYm91bmNlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgY29zdGx5IGNhbGN1bGF0aW9ucyB3aGlsZSB0aGUgd2luZG93IHNpemUgaXMgaW4gZmx1eC5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdyZXNpemUnLCBfLmRlYm91bmNlKGNhbGN1bGF0ZUxheW91dCwgMTUwKSk7XG4gKlxuICogLy8gSW52b2tlIGBzZW5kTWFpbGAgd2hlbiBjbGlja2VkLCBkZWJvdW5jaW5nIHN1YnNlcXVlbnQgY2FsbHMuXG4gKiBqUXVlcnkoZWxlbWVudCkub24oJ2NsaWNrJywgXy5kZWJvdW5jZShzZW5kTWFpbCwgMzAwLCB7XG4gKiAgICdsZWFkaW5nJzogdHJ1ZSxcbiAqICAgJ3RyYWlsaW5nJzogZmFsc2VcbiAqIH0pKTtcbiAqXG4gKiAvLyBFbnN1cmUgYGJhdGNoTG9nYCBpcyBpbnZva2VkIG9uY2UgYWZ0ZXIgMSBzZWNvbmQgb2YgZGVib3VuY2VkIGNhbGxzLlxuICogdmFyIGRlYm91bmNlZCA9IF8uZGVib3VuY2UoYmF0Y2hMb2csIDI1MCwgeyAnbWF4V2FpdCc6IDEwMDAgfSk7XG4gKiB2YXIgc291cmNlID0gbmV3IEV2ZW50U291cmNlKCcvc3RyZWFtJyk7XG4gKiBqUXVlcnkoc291cmNlKS5vbignbWVzc2FnZScsIGRlYm91bmNlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyBkZWJvdW5jZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIGRlYm91bmNlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiBkZWJvdW5jZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsYXN0QXJncyxcbiAgICAgIGxhc3RUaGlzLFxuICAgICAgbWF4V2FpdCxcbiAgICAgIHJlc3VsdCxcbiAgICAgIHRpbWVySWQsXG4gICAgICBsYXN0Q2FsbFRpbWUsXG4gICAgICBsYXN0SW52b2tlVGltZSA9IDAsXG4gICAgICBsZWFkaW5nID0gZmFsc2UsXG4gICAgICBtYXhpbmcgPSBmYWxzZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICB3YWl0ID0gdG9OdW1iZXIod2FpdCkgfHwgMDtcbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICEhb3B0aW9ucy5sZWFkaW5nO1xuICAgIG1heGluZyA9ICdtYXhXYWl0JyBpbiBvcHRpb25zO1xuICAgIG1heFdhaXQgPSBtYXhpbmcgPyBuYXRpdmVNYXgodG9OdW1iZXIob3B0aW9ucy5tYXhXYWl0KSB8fCAwLCB3YWl0KSA6IG1heFdhaXQ7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuXG4gIGZ1bmN0aW9uIGludm9rZUZ1bmModGltZSkge1xuICAgIHZhciBhcmdzID0gbGFzdEFyZ3MsXG4gICAgICAgIHRoaXNBcmcgPSBsYXN0VGhpcztcblxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkodGhpc0FyZywgYXJncyk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGxlYWRpbmdFZGdlKHRpbWUpIHtcbiAgICAvLyBSZXNldCBhbnkgYG1heFdhaXRgIHRpbWVyLlxuICAgIGxhc3RJbnZva2VUaW1lID0gdGltZTtcbiAgICAvLyBTdGFydCB0aGUgdGltZXIgZm9yIHRoZSB0cmFpbGluZyBlZGdlLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgLy8gSW52b2tlIHRoZSBsZWFkaW5nIGVkZ2UuXG4gICAgcmV0dXJuIGxlYWRpbmcgPyBpbnZva2VGdW5jKHRpbWUpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gcmVtYWluaW5nV2FpdCh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZSxcbiAgICAgICAgcmVzdWx0ID0gd2FpdCAtIHRpbWVTaW5jZUxhc3RDYWxsO1xuXG4gICAgcmV0dXJuIG1heGluZyA/IG5hdGl2ZU1pbihyZXN1bHQsIG1heFdhaXQgLSB0aW1lU2luY2VMYXN0SW52b2tlKSA6IHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIHNob3VsZEludm9rZSh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZTtcblxuICAgIC8vIEVpdGhlciB0aGlzIGlzIHRoZSBmaXJzdCBjYWxsLCBhY3Rpdml0eSBoYXMgc3RvcHBlZCBhbmQgd2UncmUgYXQgdGhlXG4gICAgLy8gdHJhaWxpbmcgZWRnZSwgdGhlIHN5c3RlbSB0aW1lIGhhcyBnb25lIGJhY2t3YXJkcyBhbmQgd2UncmUgdHJlYXRpbmdcbiAgICAvLyBpdCBhcyB0aGUgdHJhaWxpbmcgZWRnZSwgb3Igd2UndmUgaGl0IHRoZSBgbWF4V2FpdGAgbGltaXQuXG4gICAgcmV0dXJuIChsYXN0Q2FsbFRpbWUgPT09IHVuZGVmaW5lZCB8fCAodGltZVNpbmNlTGFzdENhbGwgPj0gd2FpdCkgfHxcbiAgICAgICh0aW1lU2luY2VMYXN0Q2FsbCA8IDApIHx8IChtYXhpbmcgJiYgdGltZVNpbmNlTGFzdEludm9rZSA+PSBtYXhXYWl0KSk7XG4gIH1cblxuICBmdW5jdGlvbiB0aW1lckV4cGlyZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKTtcbiAgICBpZiAoc2hvdWxkSW52b2tlKHRpbWUpKSB7XG4gICAgICByZXR1cm4gdHJhaWxpbmdFZGdlKHRpbWUpO1xuICAgIH1cbiAgICAvLyBSZXN0YXJ0IHRoZSB0aW1lci5cbiAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHJlbWFpbmluZ1dhaXQodGltZSkpO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJhaWxpbmdFZGdlKHRpbWUpIHtcbiAgICB0aW1lcklkID0gdW5kZWZpbmVkO1xuXG4gICAgLy8gT25seSBpbnZva2UgaWYgd2UgaGF2ZSBgbGFzdEFyZ3NgIHdoaWNoIG1lYW5zIGBmdW5jYCBoYXMgYmVlblxuICAgIC8vIGRlYm91bmNlZCBhdCBsZWFzdCBvbmNlLlxuICAgIGlmICh0cmFpbGluZyAmJiBsYXN0QXJncykge1xuICAgICAgcmV0dXJuIGludm9rZUZ1bmModGltZSk7XG4gICAgfVxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGNhbmNlbCgpIHtcbiAgICBpZiAodGltZXJJZCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZXJJZCk7XG4gICAgfVxuICAgIGxhc3RJbnZva2VUaW1lID0gMDtcbiAgICBsYXN0QXJncyA9IGxhc3RDYWxsVGltZSA9IGxhc3RUaGlzID0gdGltZXJJZCA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGZsdXNoKCkge1xuICAgIHJldHVybiB0aW1lcklkID09PSB1bmRlZmluZWQgPyByZXN1bHQgOiB0cmFpbGluZ0VkZ2Uobm93KCkpO1xuICB9XG5cbiAgZnVuY3Rpb24gZGVib3VuY2VkKCkge1xuICAgIHZhciB0aW1lID0gbm93KCksXG4gICAgICAgIGlzSW52b2tpbmcgPSBzaG91bGRJbnZva2UodGltZSk7XG5cbiAgICBsYXN0QXJncyA9IGFyZ3VtZW50cztcbiAgICBsYXN0VGhpcyA9IHRoaXM7XG4gICAgbGFzdENhbGxUaW1lID0gdGltZTtcblxuICAgIGlmIChpc0ludm9raW5nKSB7XG4gICAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBsZWFkaW5nRWRnZShsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgICAgaWYgKG1heGluZykge1xuICAgICAgICAvLyBIYW5kbGUgaW52b2NhdGlvbnMgaW4gYSB0aWdodCBsb29wLlxuICAgICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgICAgICByZXR1cm4gaW52b2tlRnVuYyhsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGRlYm91bmNlZC5jYW5jZWwgPSBjYW5jZWw7XG4gIGRlYm91bmNlZC5mbHVzaCA9IGZsdXNoO1xuICByZXR1cm4gZGVib3VuY2VkO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSB0aHJvdHRsZWQgZnVuY3Rpb24gdGhhdCBvbmx5IGludm9rZXMgYGZ1bmNgIGF0IG1vc3Qgb25jZSBwZXJcbiAqIGV2ZXJ5IGB3YWl0YCBtaWxsaXNlY29uZHMuIFRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgXG4gKiBtZXRob2QgdG8gY2FuY2VsIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvXG4gKiBpbW1lZGlhdGVseSBpbnZva2UgdGhlbS4gUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2BcbiAqIHNob3VsZCBiZSBpbnZva2VkIG9uIHRoZSBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGBcbiAqIHRpbWVvdXQuIFRoZSBgZnVuY2AgaXMgaW52b2tlZCB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGVcbiAqIHRocm90dGxlZCBmdW5jdGlvbi4gU3Vic2VxdWVudCBjYWxscyB0byB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uIHJldHVybiB0aGVcbiAqIHJlc3VsdCBvZiB0aGUgbGFzdCBgZnVuY2AgaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIHRocm90dGxlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy50aHJvdHRsZWAgYW5kIGBfLmRlYm91bmNlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIHRocm90dGxlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHRocm90dGxlIGludm9jYXRpb25zIHRvLlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHRocm90dGxlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgZXhjZXNzaXZlbHkgdXBkYXRpbmcgdGhlIHBvc2l0aW9uIHdoaWxlIHNjcm9sbGluZy5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdzY3JvbGwnLCBfLnRocm90dGxlKHVwZGF0ZVBvc2l0aW9uLCAxMDApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHJlbmV3VG9rZW5gIHdoZW4gdGhlIGNsaWNrIGV2ZW50IGlzIGZpcmVkLCBidXQgbm90IG1vcmUgdGhhbiBvbmNlIGV2ZXJ5IDUgbWludXRlcy5cbiAqIHZhciB0aHJvdHRsZWQgPSBfLnRocm90dGxlKHJlbmV3VG9rZW4sIDMwMDAwMCwgeyAndHJhaWxpbmcnOiBmYWxzZSB9KTtcbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCB0aHJvdHRsZWQpO1xuICpcbiAqIC8vIENhbmNlbCB0aGUgdHJhaWxpbmcgdGhyb3R0bGVkIGludm9jYXRpb24uXG4gKiBqUXVlcnkod2luZG93KS5vbigncG9wc3RhdGUnLCB0aHJvdHRsZWQuY2FuY2VsKTtcbiAqL1xuZnVuY3Rpb24gdGhyb3R0bGUoZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICB2YXIgbGVhZGluZyA9IHRydWUsXG4gICAgICB0cmFpbGluZyA9IHRydWU7XG5cbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICdsZWFkaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLmxlYWRpbmcgOiBsZWFkaW5nO1xuICAgIHRyYWlsaW5nID0gJ3RyYWlsaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLnRyYWlsaW5nIDogdHJhaWxpbmc7XG4gIH1cbiAgcmV0dXJuIGRlYm91bmNlKGZ1bmMsIHdhaXQsIHtcbiAgICAnbGVhZGluZyc6IGxlYWRpbmcsXG4gICAgJ21heFdhaXQnOiB3YWl0LFxuICAgICd0cmFpbGluZyc6IHRyYWlsaW5nXG4gIH0pO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIHRoZVxuICogW2xhbmd1YWdlIHR5cGVdKGh0dHA6Ly93d3cuZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi83LjAvI3NlYy1lY21hc2NyaXB0LWxhbmd1YWdlLXR5cGVzKVxuICogb2YgYE9iamVjdGAuIChlLmcuIGFycmF5cywgZnVuY3Rpb25zLCBvYmplY3RzLCByZWdleGVzLCBgbmV3IE51bWJlcigwKWAsIGFuZCBgbmV3IFN0cmluZygnJylgKVxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGFuIG9iamVjdCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0KHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChfLm5vb3ApO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QobnVsbCk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc09iamVjdCh2YWx1ZSkge1xuICB2YXIgdHlwZSA9IHR5cGVvZiB2YWx1ZTtcbiAgcmV0dXJuICEhdmFsdWUgJiYgKHR5cGUgPT0gJ29iamVjdCcgfHwgdHlwZSA9PSAnZnVuY3Rpb24nKTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZS4gQSB2YWx1ZSBpcyBvYmplY3QtbGlrZSBpZiBpdCdzIG5vdCBgbnVsbGBcbiAqIGFuZCBoYXMgYSBgdHlwZW9mYCByZXN1bHQgb2YgXCJvYmplY3RcIi5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZSwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZSh7fSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdExpa2UoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShfLm5vb3ApO1xuICogLy8gPT4gZmFsc2VcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0TGlrZSh2YWx1ZSkge1xuICByZXR1cm4gISF2YWx1ZSAmJiB0eXBlb2YgdmFsdWUgPT0gJ29iamVjdCc7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhIGBTeW1ib2xgIHByaW1pdGl2ZSBvciBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYSBzeW1ib2wsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc1N5bWJvbChTeW1ib2wuaXRlcmF0b3IpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNTeW1ib2woJ2FiYycpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNTeW1ib2wodmFsdWUpIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PSAnc3ltYm9sJyB8fFxuICAgIChpc09iamVjdExpa2UodmFsdWUpICYmIG9iamVjdFRvU3RyaW5nLmNhbGwodmFsdWUpID09IHN5bWJvbFRhZyk7XG59XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBhIG51bWJlci5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gcHJvY2Vzcy5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIG51bWJlci5cbiAqIEBleGFtcGxlXG4gKlxuICogXy50b051bWJlcigzLjIpO1xuICogLy8gPT4gMy4yXG4gKlxuICogXy50b051bWJlcihOdW1iZXIuTUlOX1ZBTFVFKTtcbiAqIC8vID0+IDVlLTMyNFxuICpcbiAqIF8udG9OdW1iZXIoSW5maW5pdHkpO1xuICogLy8gPT4gSW5maW5pdHlcbiAqXG4gKiBfLnRvTnVtYmVyKCczLjInKTtcbiAqIC8vID0+IDMuMlxuICovXG5mdW5jdGlvbiB0b051bWJlcih2YWx1ZSkge1xuICBpZiAodHlwZW9mIHZhbHVlID09ICdudW1iZXInKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIGlmIChpc1N5bWJvbCh2YWx1ZSkpIHtcbiAgICByZXR1cm4gTkFOO1xuICB9XG4gIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICB2YXIgb3RoZXIgPSB0eXBlb2YgdmFsdWUudmFsdWVPZiA9PSAnZnVuY3Rpb24nID8gdmFsdWUudmFsdWVPZigpIDogdmFsdWU7XG4gICAgdmFsdWUgPSBpc09iamVjdChvdGhlcikgPyAob3RoZXIgKyAnJykgOiBvdGhlcjtcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlID09PSAwID8gdmFsdWUgOiArdmFsdWU7XG4gIH1cbiAgdmFsdWUgPSB2YWx1ZS5yZXBsYWNlKHJlVHJpbSwgJycpO1xuICB2YXIgaXNCaW5hcnkgPSByZUlzQmluYXJ5LnRlc3QodmFsdWUpO1xuICByZXR1cm4gKGlzQmluYXJ5IHx8IHJlSXNPY3RhbC50ZXN0KHZhbHVlKSlcbiAgICA/IGZyZWVQYXJzZUludCh2YWx1ZS5zbGljZSgyKSwgaXNCaW5hcnkgPyAyIDogOClcbiAgICA6IChyZUlzQmFkSGV4LnRlc3QodmFsdWUpID8gTkFOIDogK3ZhbHVlKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB0aHJvdHRsZTtcbiIsInZhciBnO1xyXG5cclxuLy8gVGhpcyB3b3JrcyBpbiBub24tc3RyaWN0IG1vZGVcclxuZyA9IChmdW5jdGlvbigpIHtcclxuXHRyZXR1cm4gdGhpcztcclxufSkoKTtcclxuXHJcbnRyeSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiBldmFsIGlzIGFsbG93ZWQgKHNlZSBDU1ApXHJcblx0ZyA9IGcgfHwgRnVuY3Rpb24oXCJyZXR1cm4gdGhpc1wiKSgpIHx8ICgxLCBldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2ggKGUpIHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIHRoZSB3aW5kb3cgcmVmZXJlbmNlIGlzIGF2YWlsYWJsZVxyXG5cdGlmICh0eXBlb2Ygd2luZG93ID09PSBcIm9iamVjdFwiKSBnID0gd2luZG93O1xyXG59XHJcblxyXG4vLyBnIGNhbiBzdGlsbCBiZSB1bmRlZmluZWQsIGJ1dCBub3RoaW5nIHRvIGRvIGFib3V0IGl0Li4uXHJcbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXHJcbi8vIGVhc2llciB0byBoYW5kbGUgdGhpcyBjYXNlLiBpZighZ2xvYmFsKSB7IC4uLn1cclxuXHJcbm1vZHVsZS5leHBvcnRzID0gZztcclxuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5pbXBvcnQgeyBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcblxuZXhwb3J0IGNsYXNzIEFjdGl2ZUxpbmVNYXJrZXIge1xuXHRwcml2YXRlIF9jdXJyZW50OiBhbnk7XG5cblx0b25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmU6IG51bWJlcikge1xuXHRcdGNvbnN0IHsgcHJldmlvdXMgfSA9IGdldEVsZW1lbnRzRm9yU291cmNlTGluZShsaW5lKTtcblx0XHR0aGlzLl91cGRhdGUocHJldmlvdXMgJiYgcHJldmlvdXMuZWxlbWVudCk7XG5cdH1cblxuXHRfdXBkYXRlKGJlZm9yZTogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHR0aGlzLl91bm1hcmtBY3RpdmVFbGVtZW50KHRoaXMuX2N1cnJlbnQpO1xuXHRcdHRoaXMuX21hcmtBY3RpdmVFbGVtZW50KGJlZm9yZSk7XG5cdFx0dGhpcy5fY3VycmVudCA9IGJlZm9yZTtcblx0fVxuXG5cdF91bm1hcmtBY3RpdmVFbGVtZW50KGVsZW1lbnQ6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkKSB7XG5cdFx0aWYgKCFlbGVtZW50KSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdGVsZW1lbnQuY2xhc3NOYW1lID0gZWxlbWVudC5jbGFzc05hbWUucmVwbGFjZSgvXFxiY29kZS1hY3RpdmUtbGluZVxcYi9nLCAnJyk7XG5cdH1cblxuXHRfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudDogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHRpZiAoIWVsZW1lbnQpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0ZWxlbWVudC5jbGFzc05hbWUgKz0gJyBjb2RlLWFjdGl2ZS1saW5lJztcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGY6ICgpID0+IHZvaWQpIHtcblx0aWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdsb2FkaW5nJyB8fCBkb2N1bWVudC5yZWFkeVN0YXRlIGFzIHN0cmluZyA9PT0gJ3VuaW5pdGlhbGl6ZWQnKSB7XG5cdFx0ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGYpO1xuXHR9IGVsc2Uge1xuXHRcdGYoKTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBBY3RpdmVMaW5lTWFya2VyIH0gZnJvbSAnLi9hY3RpdmVMaW5lTWFya2VyJztcbmltcG9ydCB7IG9uY2VEb2N1bWVudExvYWRlZCB9IGZyb20gJy4vZXZlbnRzJztcbmltcG9ydCB7IGNyZWF0ZVBvc3RlckZvclZzQ29kZSB9IGZyb20gJy4vbWVzc2FnaW5nJztcbmltcG9ydCB7IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0LCBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcbmltcG9ydCB7IGdldFNldHRpbmdzLCBnZXREYXRhIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5pbXBvcnQgdGhyb3R0bGUgPSByZXF1aXJlKCdsb2Rhc2gudGhyb3R0bGUnKTtcblxuZGVjbGFyZSB2YXIgYWNxdWlyZVZzQ29kZUFwaTogYW55O1xuXG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IEFjdGl2ZUxpbmVNYXJrZXIoKTtcbmNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuY29uc3QgdnNjb2RlID0gYWNxdWlyZVZzQ29kZUFwaSgpO1xuXG4vLyBTZXQgVlMgQ29kZSBzdGF0ZVxubGV0IHN0YXRlID0gZ2V0RGF0YSgnZGF0YS1zdGF0ZScpO1xudnNjb2RlLnNldFN0YXRlKHN0YXRlKTtcblxuY29uc3QgbWVzc2FnaW5nID0gY3JlYXRlUG9zdGVyRm9yVnNDb2RlKHZzY29kZSk7XG5cbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG5cbndpbmRvdy5vbmxvYWQgPSAoKSA9PiB7XG5cdHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5cbm9uY2VEb2N1bWVudExvYWRlZCgoKSA9PiB7XG5cdGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuXHRcdHNldFRpbWVvdXQoKCkgPT4ge1xuXHRcdFx0Y29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcblx0XHRcdGlmICghaXNOYU4oaW5pdGlhbExpbmUpKSB7XG5cdFx0XHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRcdFx0c2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcblx0XHRcdH1cblx0XHR9LCAwKTtcblx0fVxufSk7XG5cbmNvbnN0IG9uVXBkYXRlVmlldyA9ICgoKSA9PiB7XG5cdGNvbnN0IGRvU2Nyb2xsID0gdGhyb3R0bGUoKGxpbmU6IG51bWJlcikgPT4ge1xuXHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG5cdH0sIDUwKTtcblxuXHRyZXR1cm4gKGxpbmU6IG51bWJlciwgc2V0dGluZ3M6IGFueSkgPT4ge1xuXHRcdGlmICghaXNOYU4obGluZSkpIHtcblx0XHRcdHNldHRpbmdzLmxpbmUgPSBsaW5lO1xuXHRcdFx0ZG9TY3JvbGwobGluZSk7XG5cdFx0fVxuXHR9O1xufSkoKTtcblxubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG5cdGNvbnN0IGltYWdlSW5mbzogeyBpZDogc3RyaW5nLCBoZWlnaHQ6IG51bWJlciwgd2lkdGg6IG51bWJlciB9W10gPSBbXTtcblx0bGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcblx0aWYgKGltYWdlcykge1xuXHRcdGxldCBpO1xuXHRcdGZvciAoaSA9IDA7IGkgPCBpbWFnZXMubGVuZ3RoOyBpKyspIHtcblx0XHRcdGNvbnN0IGltZyA9IGltYWdlc1tpXTtcblxuXHRcdFx0aWYgKGltZy5jbGFzc0xpc3QuY29udGFpbnMoJ2xvYWRpbmcnKSkge1xuXHRcdFx0XHRpbWcuY2xhc3NMaXN0LnJlbW92ZSgnbG9hZGluZycpO1xuXHRcdFx0fVxuXG5cdFx0XHRpbWFnZUluZm8ucHVzaCh7XG5cdFx0XHRcdGlkOiBpbWcuaWQsXG5cdFx0XHRcdGhlaWdodDogaW1nLmhlaWdodCxcblx0XHRcdFx0d2lkdGg6IGltZy53aWR0aFxuXHRcdFx0fSk7XG5cdFx0fVxuXG5cdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdjYWNoZUltYWdlU2l6ZXMnLCBpbWFnZUluZm8pO1xuXHR9XG59LCA1MCk7XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdyZXNpemUnLCAoKSA9PiB7XG5cdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0dXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgZXZlbnQgPT4ge1xuXHRpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuXHRcdHJldHVybjtcblx0fVxuXG5cdHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG5cdFx0Y2FzZSAnb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uJzpcblx0XHRcdG1hcmtlci5vbkRpZENoYW5nZVRleHRFZGl0b3JTZWxlY3Rpb24oZXZlbnQuZGF0YS5saW5lKTtcblx0XHRcdGJyZWFrO1xuXG5cdFx0Y2FzZSAndXBkYXRlVmlldyc6XG5cdFx0XHRvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG5cdFx0XHRicmVhaztcblx0fVxufSwgZmFsc2UpO1xuXG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcblx0aWYgKCFzZXR0aW5ncy5kb3VibGVDbGlja1RvU3dpdGNoVG9FZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHQvLyBJZ25vcmUgY2xpY2tzIG9uIGxpbmtzXG5cdGZvciAobGV0IG5vZGUgPSBldmVudC50YXJnZXQgYXMgSFRNTEVsZW1lbnQ7IG5vZGU7IG5vZGUgPSBub2RlLnBhcmVudE5vZGUgYXMgSFRNTEVsZW1lbnQpIHtcblx0XHRpZiAobm9kZS50YWdOYW1lID09PSAnQScpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdH1cblxuXHRjb25zdCBvZmZzZXQgPSBldmVudC5wYWdlWTtcblx0Y29uc3QgbGluZSA9IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmICh0eXBlb2YgbGluZSA9PT0gJ251bWJlcicgJiYgIWlzTmFOKGxpbmUpKSB7XG5cdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcblx0fVxufSk7XG5cbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgZXZlbnQgPT4ge1xuXHRpZiAoIWV2ZW50KSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0bGV0IG5vZGU6IGFueSA9IGV2ZW50LnRhcmdldDtcblx0d2hpbGUgKG5vZGUpIHtcblx0XHRpZiAobm9kZS50YWdOYW1lICYmIG5vZGUudGFnTmFtZSA9PT0gJ0EnICYmIG5vZGUuaHJlZikge1xuXHRcdFx0aWYgKG5vZGUuZ2V0QXR0cmlidXRlKCdocmVmJykuc3RhcnRzV2l0aCgnIycpKSB7XG5cdFx0XHRcdGJyZWFrO1xuXHRcdFx0fVxuXHRcdFx0aWYgKG5vZGUuaHJlZi5zdGFydHNXaXRoKCdmaWxlOi8vJykgfHwgbm9kZS5ocmVmLnN0YXJ0c1dpdGgoJ3ZzY29kZS1yZXNvdXJjZTonKSkge1xuXHRcdFx0XHRjb25zdCBbcGF0aCwgZnJhZ21lbnRdID0gbm9kZS5ocmVmLnJlcGxhY2UoL14oZmlsZTpcXC9cXC98dnNjb2RlLXJlc291cmNlOikvaSwgJycpLnNwbGl0KCcjJyk7XG5cdFx0XHRcdG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnY2xpY2tMaW5rJywgeyBwYXRoLCBmcmFnbWVudCB9KTtcblx0XHRcdFx0ZXZlbnQucHJldmVudERlZmF1bHQoKTtcblx0XHRcdFx0ZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG5cdFx0XHRcdGJyZWFrO1xuXHRcdFx0fVxuXHRcdFx0YnJlYWs7XG5cdFx0fVxuXHRcdG5vZGUgPSBub2RlLnBhcmVudE5vZGU7XG5cdH1cbn0sIHRydWUpO1xuXG5pZiAoc2V0dGluZ3Muc2Nyb2xsRWRpdG9yV2l0aFByZXZpZXcpIHtcblx0d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Njcm9sbCcsIHRocm90dGxlKCgpID0+IHtcblx0XHRpZiAoc2Nyb2xsRGlzYWJsZWQpIHtcblx0XHRcdHNjcm9sbERpc2FibGVkID0gZmFsc2U7XG5cdFx0fSBlbHNlIHtcblx0XHRcdGNvbnN0IGxpbmUgPSBnZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldCh3aW5kb3cuc2Nyb2xsWSk7XG5cdFx0XHRpZiAodHlwZW9mIGxpbmUgPT09ICdudW1iZXInICYmICFpc05hTihsaW5lKSkge1xuXHRcdFx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ3JldmVhbExpbmUnLCB7IGxpbmUgfSk7XG5cdFx0XHRcdHN0YXRlLmxpbmUgPSBsaW5lO1xuXHRcdFx0XHR2c2NvZGUuc2V0U3RhdGUoc3RhdGUpO1xuXHRcdFx0fVxuXHRcdH1cblx0fSwgNTApKTtcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgZ2V0U2V0dGluZ3MgfSBmcm9tICcuL3NldHRpbmdzJztcblxuZXhwb3J0IGludGVyZmFjZSBNZXNzYWdlUG9zdGVyIHtcblx0LyoqXG5cdCAqIFBvc3QgYSBtZXNzYWdlIHRvIHRoZSBtYXJrZG93biBleHRlbnNpb25cblx0ICovXG5cdHBvc3RNZXNzYWdlKHR5cGU6IHN0cmluZywgYm9keTogb2JqZWN0KTogdm9pZDtcbn1cblxuZXhwb3J0IGNvbnN0IGNyZWF0ZVBvc3RlckZvclZzQ29kZSA9ICh2c2NvZGU6IGFueSkgPT4ge1xuXHRyZXR1cm4gbmV3IGNsYXNzIGltcGxlbWVudHMgTWVzc2FnZVBvc3RlciB7XG5cdFx0cG9zdE1lc3NhZ2UodHlwZTogc3RyaW5nLCBib2R5OiBvYmplY3QpOiB2b2lkIHtcblx0XHRcdHZzY29kZS5wb3N0TWVzc2FnZSh7XG5cdFx0XHRcdHR5cGUsXG5cdFx0XHRcdHNvdXJjZTogZ2V0U2V0dGluZ3MoKS5zb3VyY2UsXG5cdFx0XHRcdGJvZHlcblx0XHRcdH0pO1xuXHRcdH1cblx0fTtcbn07XG5cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBnZXRTZXR0aW5ncyB9IGZyb20gJy4vc2V0dGluZ3MnO1xuXG5cbmZ1bmN0aW9uIGNsYW1wKG1pbjogbnVtYmVyLCBtYXg6IG51bWJlciwgdmFsdWU6IG51bWJlcikge1xuXHRyZXR1cm4gTWF0aC5taW4obWF4LCBNYXRoLm1heChtaW4sIHZhbHVlKSk7XG59XG5cbmZ1bmN0aW9uIGNsYW1wTGluZShsaW5lOiBudW1iZXIpIHtcblx0cmV0dXJuIGNsYW1wKDAsIGdldFNldHRpbmdzKCkubGluZUNvdW50IC0gMSwgbGluZSk7XG59XG5cblxuZXhwb3J0IGludGVyZmFjZSBDb2RlTGluZUVsZW1lbnQge1xuXHRlbGVtZW50OiBIVE1MRWxlbWVudDtcblx0bGluZTogbnVtYmVyO1xufVxuXG5jb25zdCBnZXRDb2RlTGluZUVsZW1lbnRzID0gKCgpID0+IHtcblx0bGV0IGVsZW1lbnRzOiBDb2RlTGluZUVsZW1lbnRbXTtcblx0cmV0dXJuICgpID0+IHtcblx0XHRpZiAoIWVsZW1lbnRzKSB7XG5cdFx0XHRlbGVtZW50cyA9IFt7IGVsZW1lbnQ6IGRvY3VtZW50LmJvZHksIGxpbmU6IDAgfV07XG5cdFx0XHRmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS1saW5lJykpIHtcblx0XHRcdFx0Y29uc3QgbGluZSA9ICtlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS1saW5lJykhO1xuXHRcdFx0XHRpZiAoIWlzTmFOKGxpbmUpKSB7XG5cdFx0XHRcdFx0ZWxlbWVudHMucHVzaCh7IGVsZW1lbnQ6IGVsZW1lbnQgYXMgSFRNTEVsZW1lbnQsIGxpbmUgfSk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdFx0cmV0dXJuIGVsZW1lbnRzO1xuXHR9O1xufSkoKTtcblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgbWFwIHRvIGEgc3BlY2lmaWMgdGFyZ2V0IGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqXG4gKiBJZiBhbiBleGFjdCBtYXRjaCwgcmV0dXJucyBhIHNpbmdsZSBlbGVtZW50LiBJZiB0aGUgbGluZSBpcyBiZXR3ZWVuIGVsZW1lbnRzLFxuICogcmV0dXJucyB0aGUgZWxlbWVudCBwcmlvciB0byBhbmQgdGhlIGVsZW1lbnQgYWZ0ZXIgdGhlIGdpdmVuIGxpbmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUodGFyZ2V0TGluZTogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZU51bWJlciA9IE1hdGguZmxvb3IodGFyZ2V0TGluZSk7XG5cdGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuXHRsZXQgcHJldmlvdXMgPSBsaW5lc1swXSB8fCBudWxsO1xuXHRmb3IgKGNvbnN0IGVudHJ5IG9mIGxpbmVzKSB7XG5cdFx0aWYgKGVudHJ5LmxpbmUgPT09IGxpbmVOdW1iZXIpIHtcblx0XHRcdHJldHVybiB7IHByZXZpb3VzOiBlbnRyeSwgbmV4dDogdW5kZWZpbmVkIH07XG5cdFx0fSBlbHNlIGlmIChlbnRyeS5saW5lID4gbGluZU51bWJlcikge1xuXHRcdFx0cmV0dXJuIHsgcHJldmlvdXMsIG5leHQ6IGVudHJ5IH07XG5cdFx0fVxuXHRcdHByZXZpb3VzID0gZW50cnk7XG5cdH1cblx0cmV0dXJuIHsgcHJldmlvdXMgfTtcbn1cblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgYXJlIGF0IGEgc3BlY2lmaWMgcGl4ZWwgb2Zmc2V0IG9uIHRoZSBwYWdlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldDogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG5cdGNvbnN0IHBvc2l0aW9uID0gb2Zmc2V0IC0gd2luZG93LnNjcm9sbFk7XG5cdGxldCBsbyA9IC0xO1xuXHRsZXQgaGkgPSBsaW5lcy5sZW5ndGggLSAxO1xuXHR3aGlsZSAobG8gKyAxIDwgaGkpIHtcblx0XHRjb25zdCBtaWQgPSBNYXRoLmZsb29yKChsbyArIGhpKSAvIDIpO1xuXHRcdGNvbnN0IGJvdW5kcyA9IGxpbmVzW21pZF0uZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRpZiAoYm91bmRzLnRvcCArIGJvdW5kcy5oZWlnaHQgPj0gcG9zaXRpb24pIHtcblx0XHRcdGhpID0gbWlkO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGxvID0gbWlkO1xuXHRcdH1cblx0fVxuXHRjb25zdCBoaUVsZW1lbnQgPSBsaW5lc1toaV07XG5cdGNvbnN0IGhpQm91bmRzID0gaGlFbGVtZW50LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cdGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG5cdFx0Y29uc3QgbG9FbGVtZW50ID0gbGluZXNbbG9dO1xuXHRcdHJldHVybiB7IHByZXZpb3VzOiBsb0VsZW1lbnQsIG5leHQ6IGhpRWxlbWVudCB9O1xuXHR9XG5cdHJldHVybiB7IHByZXZpb3VzOiBoaUVsZW1lbnQgfTtcbn1cblxuLyoqXG4gKiBBdHRlbXB0IHRvIHJldmVhbCB0aGUgZWxlbWVudCBmb3IgYSBzb3VyY2UgbGluZSBpbiB0aGUgZWRpdG9yLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGxpbmU6IG51bWJlcikge1xuXHRpZiAoIWdldFNldHRpbmdzKCkuc2Nyb2xsUHJldmlld1dpdGhFZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHRpZiAobGluZSA8PSAwKSB7XG5cdFx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgMCk7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKGxpbmUpO1xuXHRpZiAoIXByZXZpb3VzKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cdGxldCBzY3JvbGxUbyA9IDA7XG5cdGNvbnN0IHJlY3QgPSBwcmV2aW91cy5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuXHRjb25zdCBwcmV2aW91c1RvcCA9IHJlY3QudG9wO1xuXHRpZiAobmV4dCAmJiBuZXh0LmxpbmUgIT09IHByZXZpb3VzLmxpbmUpIHtcblx0XHQvLyBCZXR3ZWVuIHR3byBlbGVtZW50cy4gR28gdG8gcGVyY2VudGFnZSBvZmZzZXQgYmV0d2VlbiB0aGVtLlxuXHRcdGNvbnN0IGJldHdlZW5Qcm9ncmVzcyA9IChsaW5lIC0gcHJldmlvdXMubGluZSkgLyAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0Y29uc3QgZWxlbWVudE9mZnNldCA9IG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c1RvcDtcblx0XHRzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgYmV0d2VlblByb2dyZXNzICogZWxlbWVudE9mZnNldDtcblx0fSBlbHNlIHtcblx0XHRjb25zdCBwcm9ncmVzc0luRWxlbWVudCA9IGxpbmUgLSBNYXRoLmZsb29yKGxpbmUpO1xuXHRcdHNjcm9sbFRvID0gcHJldmlvdXNUb3AgKyAocmVjdC5oZWlnaHQgKiBwcm9ncmVzc0luRWxlbWVudCk7XG5cdH1cblx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgTWF0aC5tYXgoMSwgd2luZG93LnNjcm9sbFkgKyBzY3JvbGxUbykpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0OiBudW1iZXIpIHtcblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmIChwcmV2aW91cykge1xuXHRcdGNvbnN0IHByZXZpb3VzQm91bmRzID0gcHJldmlvdXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRjb25zdCBvZmZzZXRGcm9tUHJldmlvdXMgPSAob2Zmc2V0IC0gd2luZG93LnNjcm9sbFkgLSBwcmV2aW91c0JvdW5kcy50b3ApO1xuXHRcdGlmIChuZXh0KSB7XG5cdFx0XHRjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcblx0XHRcdGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgKiAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGNvbnN0IHByb2dyZXNzV2l0aGluRWxlbWVudCA9IG9mZnNldEZyb21QcmV2aW91cyAvIChwcmV2aW91c0JvdW5kcy5oZWlnaHQpO1xuXHRcdFx0Y29uc3QgbGluZSA9IHByZXZpb3VzLmxpbmUgKyBwcm9ncmVzc1dpdGhpbkVsZW1lbnQ7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0fVxuXHRyZXR1cm4gbnVsbDtcbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgaW50ZXJmYWNlIFByZXZpZXdTZXR0aW5ncyB7XG5cdHNvdXJjZTogc3RyaW5nO1xuXHRsaW5lOiBudW1iZXI7XG5cdGxpbmVDb3VudDogbnVtYmVyO1xuXHRzY3JvbGxQcmV2aWV3V2l0aEVkaXRvcj86IGJvb2xlYW47XG5cdHNjcm9sbEVkaXRvcldpdGhQcmV2aWV3OiBib29sZWFuO1xuXHRkaXNhYmxlU2VjdXJpdHlXYXJuaW5nczogYm9vbGVhbjtcblx0ZG91YmxlQ2xpY2tUb1N3aXRjaFRvRWRpdG9yOiBib29sZWFuO1xufVxuXG5sZXQgY2FjaGVkU2V0dGluZ3M6IFByZXZpZXdTZXR0aW5ncyB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcblxuZXhwb3J0IGZ1bmN0aW9uIGdldERhdGEoa2V5OiBzdHJpbmcpOiBQcmV2aWV3U2V0dGluZ3Mge1xuXHRjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcblx0aWYgKGVsZW1lbnQpIHtcblx0XHRjb25zdCBkYXRhID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoa2V5KTtcblx0XHRpZiAoZGF0YSkge1xuXHRcdFx0cmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG5cdFx0fVxuXHR9XG5cblx0dGhyb3cgbmV3IEVycm9yKGBDb3VsZCBub3QgbG9hZCBkYXRhIGZvciAke2tleX1gKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldFNldHRpbmdzKCk6IFByZXZpZXdTZXR0aW5ncyB7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuXHRpZiAoY2FjaGVkU2V0dGluZ3MpIHtcblx0XHRyZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBsb2FkIHNldHRpbmdzJyk7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vbm9kZV9tb2R1bGVzL2xvZGFzaC50aHJvdHRsZS9pbmRleC5qcyIsIndlYnBhY2s6Ly8vKHdlYnBhY2spL2J1aWxkaW4vZ2xvYmFsLmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2FjdGl2ZUxpbmVNYXJrZXIudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvZXZlbnRzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2luZGV4LnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zY3JvbGwtc3luYy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQSx5REFBaUQsY0FBYztBQUMvRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBMkIsMEJBQTBCLEVBQUU7QUFDdkQseUNBQWlDLGVBQWU7QUFDaEQ7QUFDQTtBQUNBOztBQUVBO0FBQ0EsOERBQXNELCtEQUErRDs7QUFFckg7QUFDQTs7O0FBR0E7QUFDQTs7Ozs7Ozs7Ozs7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsT0FBTztBQUNsQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBLDhDQUE4QyxrQkFBa0I7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQsb0JBQW9CO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsRUFBRTtBQUNiLGFBQWEsUUFBUTtBQUNyQjtBQUNBO0FBQ0Esb0JBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxFQUFFO0FBQ2IsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOzs7Ozs7Ozs7Ozs7O0FDdGJBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQ7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsNENBQTRDOztBQUU1Qzs7Ozs7Ozs7Ozs7Ozs7O0FDbkJBOzs7Z0dBR2dHO0FBQ2hHLCtGQUF5RDtBQUV6RCxNQUFhLGdCQUFnQjtJQUc1Qiw4QkFBOEIsQ0FBQyxJQUFZO1FBQzFDLE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxzQ0FBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNwRCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVELE9BQU8sQ0FBQyxNQUErQjtRQUN0QyxJQUFJLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3pDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQyxJQUFJLENBQUMsUUFBUSxHQUFHLE1BQU0sQ0FBQztJQUN4QixDQUFDO0lBRUQsb0JBQW9CLENBQUMsT0FBZ0M7UUFDcEQsSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNiLE9BQU87U0FDUDtRQUNELE9BQU8sQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsdUJBQXVCLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUVELGtCQUFrQixDQUFDLE9BQWdDO1FBQ2xELElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDYixPQUFPO1NBQ1A7UUFDRCxPQUFPLENBQUMsU0FBUyxJQUFJLG1CQUFtQixDQUFDO0lBQzFDLENBQUM7Q0FDRDtBQTNCRCw0Q0EyQkM7Ozs7Ozs7Ozs7Ozs7O0FDakNEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixrQkFBa0IsQ0FBQyxDQUFhO0lBQy9DLElBQUksUUFBUSxDQUFDLFVBQVUsS0FBSyxTQUFTLElBQUksUUFBUSxDQUFDLFVBQW9CLEtBQUssZUFBZSxFQUFFO1FBQzNGLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNqRDtTQUFNO1FBQ04sQ0FBQyxFQUFFLENBQUM7S0FDSjtBQUNGLENBQUM7QUFORCxnREFNQzs7Ozs7Ozs7Ozs7Ozs7QUNYRDs7O2dHQUdnRzs7QUFFaEcsOEdBQXNEO0FBQ3RELGdGQUE4QztBQUM5Qyx5RkFBb0Q7QUFDcEQsK0ZBQTJGO0FBQzNGLHNGQUFrRDtBQUNsRCx1R0FBNkM7QUFJN0MsSUFBSSxjQUFjLEdBQUcsSUFBSSxDQUFDO0FBQzFCLE1BQU0sTUFBTSxHQUFHLElBQUksbUNBQWdCLEVBQUUsQ0FBQztBQUN0QyxNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7QUFFL0IsTUFBTSxNQUFNLEdBQUcsZ0JBQWdCLEVBQUUsQ0FBQztBQUVsQyxvQkFBb0I7QUFDcEIsSUFBSSxLQUFLLEdBQUcsa0JBQU8sQ0FBbUIsWUFBWSxDQUFDLENBQUM7QUFDcEQsTUFBTSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUV2QixNQUFNLFNBQVMsR0FBRyxpQ0FBcUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUVoRCxNQUFNLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUN2QyxNQUFNLENBQUMsbUJBQW1CLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBRWhELE1BQU0sQ0FBQyxNQUFNLEdBQUcsR0FBRyxFQUFFO0lBQ3BCLGdCQUFnQixFQUFFLENBQUM7QUFDcEIsQ0FBQyxDQUFDO0FBRUYsMkJBQWtCLENBQUMsR0FBRyxFQUFFO0lBQ3ZCLElBQUksUUFBUSxDQUFDLHVCQUF1QixFQUFFO1FBQ3JDLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDZixNQUFNLFdBQVcsR0FBRyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7WUFDbkMsSUFBSSxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsRUFBRTtnQkFDeEIsY0FBYyxHQUFHLElBQUksQ0FBQztnQkFDdEIsc0NBQXdCLENBQUMsV0FBVyxDQUFDLENBQUM7YUFDdEM7UUFDRixDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7S0FDTjtBQUNGLENBQUMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFHLEVBQUU7SUFDMUIsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLENBQUMsSUFBWSxFQUFFLEVBQUU7UUFDMUMsY0FBYyxHQUFHLElBQUksQ0FBQztRQUN0QixzQ0FBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNoQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFFUCxPQUFPLENBQUMsSUFBWSxFQUFFLFFBQWEsRUFBRSxFQUFFO1FBQ3RDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFDakIsUUFBUSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7WUFDckIsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1NBQ2Y7SUFDRixDQUFDLENBQUM7QUFDSCxDQUFDLENBQUMsRUFBRSxDQUFDO0FBRUwsSUFBSSxnQkFBZ0IsR0FBRyxRQUFRLENBQUMsR0FBRyxFQUFFO0lBQ3BDLE1BQU0sU0FBUyxHQUFvRCxFQUFFLENBQUM7SUFDdEUsSUFBSSxNQUFNLEdBQUcsUUFBUSxDQUFDLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2xELElBQUksTUFBTSxFQUFFO1FBQ1gsSUFBSSxDQUFDLENBQUM7UUFDTixLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDbkMsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBRXRCLElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLEVBQUU7Z0JBQ3RDLEdBQUcsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO2FBQ2hDO1lBRUQsU0FBUyxDQUFDLElBQUksQ0FBQztnQkFDZCxFQUFFLEVBQUUsR0FBRyxDQUFDLEVBQUU7Z0JBQ1YsTUFBTSxFQUFFLEdBQUcsQ0FBQyxNQUFNO2dCQUNsQixLQUFLLEVBQUUsR0FBRyxDQUFDLEtBQUs7YUFDaEIsQ0FBQyxDQUFDO1NBQ0g7UUFFRCxTQUFTLENBQUMsV0FBVyxDQUFDLGlCQUFpQixFQUFFLFNBQVMsQ0FBQyxDQUFDO0tBQ3BEO0FBQ0YsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBRVAsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxHQUFHLEVBQUU7SUFDdEMsY0FBYyxHQUFHLElBQUksQ0FBQztJQUN0QixnQkFBZ0IsRUFBRSxDQUFDO0FBQ3BCLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUVULE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLEVBQUU7SUFDMUMsSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sS0FBSyxRQUFRLENBQUMsTUFBTSxFQUFFO1FBQzFDLE9BQU87S0FDUDtJQUVELFFBQVEsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUU7UUFDeEIsS0FBSyxnQ0FBZ0M7WUFDcEMsTUFBTSxDQUFDLDhCQUE4QixDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDdkQsTUFBTTtRQUVQLEtBQUssWUFBWTtZQUNoQixZQUFZLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDeEMsTUFBTTtLQUNQO0FBQ0YsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBRVYsUUFBUSxDQUFDLGdCQUFnQixDQUFDLFVBQVUsRUFBRSxLQUFLLENBQUMsRUFBRTtJQUM3QyxJQUFJLENBQUMsUUFBUSxDQUFDLDJCQUEyQixFQUFFO1FBQzFDLE9BQU87S0FDUDtJQUVELHlCQUF5QjtJQUN6QixLQUFLLElBQUksSUFBSSxHQUFHLEtBQUssQ0FBQyxNQUFxQixFQUFFLElBQUksRUFBRSxJQUFJLEdBQUcsSUFBSSxDQUFDLFVBQXlCLEVBQUU7UUFDekYsSUFBSSxJQUFJLENBQUMsT0FBTyxLQUFLLEdBQUcsRUFBRTtZQUN6QixPQUFPO1NBQ1A7S0FDRDtJQUVELE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUM7SUFDM0IsTUFBTSxJQUFJLEdBQUcsOENBQWdDLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDdEQsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUU7UUFDN0MsU0FBUyxDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7S0FDOUQ7QUFDRixDQUFDLENBQUMsQ0FBQztBQUVILFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLEVBQUU7SUFDMUMsSUFBSSxDQUFDLEtBQUssRUFBRTtRQUNYLE9BQU87S0FDUDtJQUVELElBQUksSUFBSSxHQUFRLEtBQUssQ0FBQyxNQUFNLENBQUM7SUFDN0IsT0FBTyxJQUFJLEVBQUU7UUFDWixJQUFJLElBQUksQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFDLE9BQU8sS0FBSyxHQUFHLElBQUksSUFBSSxDQUFDLElBQUksRUFBRTtZQUN0RCxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFO2dCQUM5QyxNQUFNO2FBQ047WUFDRCxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLGtCQUFrQixDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUFDLEVBQUU7Z0JBQ3RJLE1BQU0sQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsZ0NBQWdDLEVBQUUsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksTUFBTSxDQUFDLElBQUksWUFBWSxDQUFDLFFBQVEsQ0FBQyxtQkFBbUIsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztnQkFDbEssU0FBUyxDQUFDLFdBQVcsQ0FBQyxXQUFXLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztnQkFDdkQsS0FBSyxDQUFDLGNBQWMsRUFBRSxDQUFDO2dCQUN2QixLQUFLLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQ3hCLE1BQU07YUFDTjtZQUNELE1BQU07U0FDTjtRQUNELElBQUksR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDO0tBQ3ZCO0FBQ0YsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBRVQsSUFBSSxRQUFRLENBQUMsdUJBQXVCLEVBQUU7SUFDckMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxRQUFRLENBQUMsR0FBRyxFQUFFO1FBQy9DLElBQUksY0FBYyxFQUFFO1lBQ25CLGNBQWMsR0FBRyxLQUFLLENBQUM7U0FDdkI7YUFBTTtZQUNOLE1BQU0sSUFBSSxHQUFHLDhDQUFnQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUM5RCxJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRTtnQkFDN0MsU0FBUyxDQUFDLFdBQVcsQ0FBQyxZQUFZLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO2dCQUM5QyxLQUFLLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztnQkFDbEIsTUFBTSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQzthQUN2QjtTQUNEO0lBQ0YsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7Q0FDUjtBQUVELFNBQVMsWUFBWSxDQUFDLElBQVk7SUFDakMsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLDBCQUEwQixFQUFFLE1BQU0sQ0FBQyxDQUFDO0FBQ3pELENBQUM7Ozs7Ozs7Ozs7Ozs7O0FDbktEOzs7Z0dBR2dHOztBQUVoRyxzRkFBeUM7QUFTNUIsNkJBQXFCLEdBQUcsQ0FBQyxNQUFXLEVBQUUsRUFBRTtJQUNwRCxPQUFPLElBQUk7UUFDVixXQUFXLENBQUMsSUFBWSxFQUFFLElBQVk7WUFDckMsTUFBTSxDQUFDLFdBQVcsQ0FBQztnQkFDbEIsSUFBSTtnQkFDSixNQUFNLEVBQUUsc0JBQVcsRUFBRSxDQUFDLE1BQU07Z0JBQzVCLElBQUk7YUFDSixDQUFDLENBQUM7UUFDSixDQUFDO0tBQ0QsQ0FBQztBQUNILENBQUMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7QUN4QkY7OztnR0FHZ0c7O0FBRWhHLHNGQUF5QztBQUd6QyxTQUFTLEtBQUssQ0FBQyxHQUFXLEVBQUUsR0FBVyxFQUFFLEtBQWE7SUFDckQsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDO0FBQzVDLENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxJQUFZO0lBQzlCLE9BQU8sS0FBSyxDQUFDLENBQUMsRUFBRSxzQkFBVyxFQUFFLENBQUMsU0FBUyxHQUFHLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUNwRCxDQUFDO0FBUUQsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLEdBQUcsRUFBRTtJQUNqQyxJQUFJLFFBQTJCLENBQUM7SUFDaEMsT0FBTyxHQUFHLEVBQUU7UUFDWCxJQUFJLENBQUMsUUFBUSxFQUFFO1lBQ2QsUUFBUSxHQUFHLENBQUMsRUFBRSxPQUFPLEVBQUUsUUFBUSxDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNqRCxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxXQUFXLENBQUMsRUFBRTtnQkFDbkUsTUFBTSxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLFdBQVcsQ0FBRSxDQUFDO2dCQUNqRCxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO29CQUNqQixRQUFRLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLE9BQXNCLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztpQkFDekQ7YUFDRDtTQUNEO1FBQ0QsT0FBTyxRQUFRLENBQUM7SUFDakIsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUVMOzs7OztHQUtHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsVUFBa0I7SUFDMUQsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUMxQyxNQUFNLEtBQUssR0FBRyxtQkFBbUIsRUFBRSxDQUFDO0lBQ3BDLElBQUksUUFBUSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDaEMsS0FBSyxNQUFNLEtBQUssSUFBSSxLQUFLLEVBQUU7UUFDMUIsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLFVBQVUsRUFBRTtZQUM5QixPQUFPLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUM7U0FDNUM7YUFBTSxJQUFJLEtBQUssQ0FBQyxJQUFJLEdBQUcsVUFBVSxFQUFFO1lBQ25DLE9BQU8sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO1NBQ2pDO1FBQ0QsUUFBUSxHQUFHLEtBQUssQ0FBQztLQUNqQjtJQUNELE9BQU8sRUFBRSxRQUFRLEVBQUUsQ0FBQztBQUNyQixDQUFDO0FBYkQsNERBYUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLDJCQUEyQixDQUFDLE1BQWM7SUFDekQsTUFBTSxLQUFLLEdBQUcsbUJBQW1CLEVBQUUsQ0FBQztJQUNwQyxNQUFNLFFBQVEsR0FBRyxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQztJQUN6QyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNaLElBQUksRUFBRSxHQUFHLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQzFCLE9BQU8sRUFBRSxHQUFHLENBQUMsR0FBRyxFQUFFLEVBQUU7UUFDbkIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUN0QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDMUQsSUFBSSxNQUFNLENBQUMsR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksUUFBUSxFQUFFO1lBQzNDLEVBQUUsR0FBRyxHQUFHLENBQUM7U0FDVDthQUNJO1lBQ0osRUFBRSxHQUFHLEdBQUcsQ0FBQztTQUNUO0tBQ0Q7SUFDRCxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDNUIsTUFBTSxRQUFRLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO0lBQzNELElBQUksRUFBRSxJQUFJLENBQUMsSUFBSSxRQUFRLENBQUMsR0FBRyxHQUFHLFFBQVEsRUFBRTtRQUN2QyxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDNUIsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFDO0tBQ2hEO0lBQ0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBdEJELGtFQXNCQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsSUFBWTtJQUNwRCxJQUFJLENBQUMsc0JBQVcsRUFBRSxDQUFDLHVCQUF1QixFQUFFO1FBQzNDLE9BQU87S0FDUDtJQUVELElBQUksSUFBSSxJQUFJLENBQUMsRUFBRTtRQUNkLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNqQyxPQUFPO0tBQ1A7SUFFRCxNQUFNLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxHQUFHLHdCQUF3QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUU7UUFDZCxPQUFPO0tBQ1A7SUFDRCxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFDakIsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO0lBQ3RELE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUM7SUFDN0IsSUFBSSxJQUFJLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsSUFBSSxFQUFFO1FBQ3hDLDhEQUE4RDtRQUM5RCxNQUFNLGVBQWUsR0FBRyxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM3RSxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLFdBQVcsQ0FBQztRQUM3RSxRQUFRLEdBQUcsV0FBVyxHQUFHLGVBQWUsR0FBRyxhQUFhLENBQUM7S0FDekQ7U0FBTTtRQUNOLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDbEQsUUFBUSxHQUFHLFdBQVcsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsaUJBQWlCLENBQUMsQ0FBQztLQUMzRDtJQUNELE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsT0FBTyxHQUFHLFFBQVEsQ0FBQyxDQUFDLENBQUM7QUFDdkUsQ0FBQztBQTNCRCw0REEyQkM7QUFFRCxTQUFnQixnQ0FBZ0MsQ0FBQyxNQUFjO0lBQzlELE1BQU0sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEdBQUcsMkJBQTJCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDL0QsSUFBSSxRQUFRLEVBQUU7UUFDYixNQUFNLGNBQWMsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDaEUsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMxRSxJQUFJLElBQUksRUFBRTtZQUNULE1BQU0sdUJBQXVCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNySCxNQUFNLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxHQUFHLHVCQUF1QixHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDbkYsT0FBTyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDdkI7YUFDSTtZQUNKLE1BQU0scUJBQXFCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDM0UsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksR0FBRyxxQkFBcUIsQ0FBQztZQUNuRCxPQUFPLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN2QjtLQUNEO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDYixDQUFDO0FBakJELDRFQWlCQzs7Ozs7Ozs7Ozs7Ozs7QUN2SUQ7OztnR0FHZ0c7O0FBYWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFTLEdBQVc7SUFDMUMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0MiLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IFwiLi9wcmV2aWV3LXNyYy9pbmRleC50c1wiKTtcbiIsIi8qKlxuICogbG9kYXNoIChDdXN0b20gQnVpbGQpIDxodHRwczovL2xvZGFzaC5jb20vPlxuICogQnVpbGQ6IGBsb2Rhc2ggbW9kdWxhcml6ZSBleHBvcnRzPVwibnBtXCIgLW8gLi9gXG4gKiBDb3B5cmlnaHQgalF1ZXJ5IEZvdW5kYXRpb24gYW5kIG90aGVyIGNvbnRyaWJ1dG9ycyA8aHR0cHM6Ly9qcXVlcnkub3JnLz5cbiAqIFJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlIDxodHRwczovL2xvZGFzaC5jb20vbGljZW5zZT5cbiAqIEJhc2VkIG9uIFVuZGVyc2NvcmUuanMgMS44LjMgPGh0dHA6Ly91bmRlcnNjb3JlanMub3JnL0xJQ0VOU0U+XG4gKiBDb3B5cmlnaHQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbiAqL1xuXG4vKiogVXNlZCBhcyB0aGUgYFR5cGVFcnJvcmAgbWVzc2FnZSBmb3IgXCJGdW5jdGlvbnNcIiBtZXRob2RzLiAqL1xudmFyIEZVTkNfRVJST1JfVEVYVCA9ICdFeHBlY3RlZCBhIGZ1bmN0aW9uJztcblxuLyoqIFVzZWQgYXMgcmVmZXJlbmNlcyBmb3IgdmFyaW91cyBgTnVtYmVyYCBjb25zdGFudHMuICovXG52YXIgTkFOID0gMCAvIDA7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBzeW1ib2xUYWcgPSAnW29iamVjdCBTeW1ib2xdJztcblxuLyoqIFVzZWQgdG8gbWF0Y2ggbGVhZGluZyBhbmQgdHJhaWxpbmcgd2hpdGVzcGFjZS4gKi9cbnZhciByZVRyaW0gPSAvXlxccyt8XFxzKyQvZztcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJhZCBzaWduZWQgaGV4YWRlY2ltYWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmFkSGV4ID0gL15bLStdMHhbMC05YS1mXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBiaW5hcnkgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmluYXJ5ID0gL14wYlswMV0rJC9pO1xuXG4vKiogVXNlZCB0byBkZXRlY3Qgb2N0YWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzT2N0YWwgPSAvXjBvWzAtN10rJC9pO1xuXG4vKiogQnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMgd2l0aG91dCBhIGRlcGVuZGVuY3kgb24gYHJvb3RgLiAqL1xudmFyIGZyZWVQYXJzZUludCA9IHBhcnNlSW50O1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYGdsb2JhbGAgZnJvbSBOb2RlLmpzLiAqL1xudmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbCAmJiBnbG9iYWwuT2JqZWN0ID09PSBPYmplY3QgJiYgZ2xvYmFsO1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYHNlbGZgLiAqL1xudmFyIGZyZWVTZWxmID0gdHlwZW9mIHNlbGYgPT0gJ29iamVjdCcgJiYgc2VsZiAmJiBzZWxmLk9iamVjdCA9PT0gT2JqZWN0ICYmIHNlbGY7XG5cbi8qKiBVc2VkIGFzIGEgcmVmZXJlbmNlIHRvIHRoZSBnbG9iYWwgb2JqZWN0LiAqL1xudmFyIHJvb3QgPSBmcmVlR2xvYmFsIHx8IGZyZWVTZWxmIHx8IEZ1bmN0aW9uKCdyZXR1cm4gdGhpcycpKCk7XG5cbi8qKiBVc2VkIGZvciBidWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZVxuICogW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmplY3RUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXgsXG4gICAgbmF0aXZlTWluID0gTWF0aC5taW47XG5cbi8qKlxuICogR2V0cyB0aGUgdGltZXN0YW1wIG9mIHRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRoYXQgaGF2ZSBlbGFwc2VkIHNpbmNlXG4gKiB0aGUgVW5peCBlcG9jaCAoMSBKYW51YXJ5IDE5NzAgMDA6MDA6MDAgVVRDKS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDIuNC4wXG4gKiBAY2F0ZWdvcnkgRGF0ZVxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgdGltZXN0YW1wLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmRlZmVyKGZ1bmN0aW9uKHN0YW1wKSB7XG4gKiAgIGNvbnNvbGUubG9nKF8ubm93KCkgLSBzdGFtcCk7XG4gKiB9LCBfLm5vdygpKTtcbiAqIC8vID0+IExvZ3MgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgaXQgdG9vayBmb3IgdGhlIGRlZmVycmVkIGludm9jYXRpb24uXG4gKi9cbnZhciBub3cgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHJvb3QuRGF0ZS5ub3coKTtcbn07XG5cbi8qKlxuICogQ3JlYXRlcyBhIGRlYm91bmNlZCBmdW5jdGlvbiB0aGF0IGRlbGF5cyBpbnZva2luZyBgZnVuY2AgdW50aWwgYWZ0ZXIgYHdhaXRgXG4gKiBtaWxsaXNlY29uZHMgaGF2ZSBlbGFwc2VkIHNpbmNlIHRoZSBsYXN0IHRpbWUgdGhlIGRlYm91bmNlZCBmdW5jdGlvbiB3YXNcbiAqIGludm9rZWQuIFRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgIG1ldGhvZCB0byBjYW5jZWxcbiAqIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLlxuICogUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2Agc2hvdWxkIGJlIGludm9rZWQgb24gdGhlXG4gKiBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGAgdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkXG4gKiB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50XG4gKiBjYWxscyB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHJldHVybiB0aGUgcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYFxuICogaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIGRlYm91bmNlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy5kZWJvdW5jZWAgYW5kIGBfLnRocm90dGxlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGRlYm91bmNlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIGRlbGF5LlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9ZmFsc2VdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgbGVhZGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHBhcmFtIHtudW1iZXJ9IFtvcHRpb25zLm1heFdhaXRdXG4gKiAgVGhlIG1heGltdW0gdGltZSBgZnVuY2AgaXMgYWxsb3dlZCB0byBiZSBkZWxheWVkIGJlZm9yZSBpdCdzIGludm9rZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGRlYm91bmNlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgY29zdGx5IGNhbGN1bGF0aW9ucyB3aGlsZSB0aGUgd2luZG93IHNpemUgaXMgaW4gZmx1eC5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdyZXNpemUnLCBfLmRlYm91bmNlKGNhbGN1bGF0ZUxheW91dCwgMTUwKSk7XG4gKlxuICogLy8gSW52b2tlIGBzZW5kTWFpbGAgd2hlbiBjbGlja2VkLCBkZWJvdW5jaW5nIHN1YnNlcXVlbnQgY2FsbHMuXG4gKiBqUXVlcnkoZWxlbWVudCkub24oJ2NsaWNrJywgXy5kZWJvdW5jZShzZW5kTWFpbCwgMzAwLCB7XG4gKiAgICdsZWFkaW5nJzogdHJ1ZSxcbiAqICAgJ3RyYWlsaW5nJzogZmFsc2VcbiAqIH0pKTtcbiAqXG4gKiAvLyBFbnN1cmUgYGJhdGNoTG9nYCBpcyBpbnZva2VkIG9uY2UgYWZ0ZXIgMSBzZWNvbmQgb2YgZGVib3VuY2VkIGNhbGxzLlxuICogdmFyIGRlYm91bmNlZCA9IF8uZGVib3VuY2UoYmF0Y2hMb2csIDI1MCwgeyAnbWF4V2FpdCc6IDEwMDAgfSk7XG4gKiB2YXIgc291cmNlID0gbmV3IEV2ZW50U291cmNlKCcvc3RyZWFtJyk7XG4gKiBqUXVlcnkoc291cmNlKS5vbignbWVzc2FnZScsIGRlYm91bmNlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyBkZWJvdW5jZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIGRlYm91bmNlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiBkZWJvdW5jZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsYXN0QXJncyxcbiAgICAgIGxhc3RUaGlzLFxuICAgICAgbWF4V2FpdCxcbiAgICAgIHJlc3VsdCxcbiAgICAgIHRpbWVySWQsXG4gICAgICBsYXN0Q2FsbFRpbWUsXG4gICAgICBsYXN0SW52b2tlVGltZSA9IDAsXG4gICAgICBsZWFkaW5nID0gZmFsc2UsXG4gICAgICBtYXhpbmcgPSBmYWxzZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICB3YWl0ID0gdG9OdW1iZXIod2FpdCkgfHwgMDtcbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICEhb3B0aW9ucy5sZWFkaW5nO1xuICAgIG1heGluZyA9ICdtYXhXYWl0JyBpbiBvcHRpb25zO1xuICAgIG1heFdhaXQgPSBtYXhpbmcgPyBuYXRpdmVNYXgodG9OdW1iZXIob3B0aW9ucy5tYXhXYWl0KSB8fCAwLCB3YWl0KSA6IG1heFdhaXQ7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuXG4gIGZ1bmN0aW9uIGludm9rZUZ1bmModGltZSkge1xuICAgIHZhciBhcmdzID0gbGFzdEFyZ3MsXG4gICAgICAgIHRoaXNBcmcgPSBsYXN0VGhpcztcblxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkodGhpc0FyZywgYXJncyk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGxlYWRpbmdFZGdlKHRpbWUpIHtcbiAgICAvLyBSZXNldCBhbnkgYG1heFdhaXRgIHRpbWVyLlxuICAgIGxhc3RJbnZva2VUaW1lID0gdGltZTtcbiAgICAvLyBTdGFydCB0aGUgdGltZXIgZm9yIHRoZSB0cmFpbGluZyBlZGdlLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgLy8gSW52b2tlIHRoZSBsZWFkaW5nIGVkZ2UuXG4gICAgcmV0dXJuIGxlYWRpbmcgPyBpbnZva2VGdW5jKHRpbWUpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gcmVtYWluaW5nV2FpdCh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZSxcbiAgICAgICAgcmVzdWx0ID0gd2FpdCAtIHRpbWVTaW5jZUxhc3RDYWxsO1xuXG4gICAgcmV0dXJuIG1heGluZyA/IG5hdGl2ZU1pbihyZXN1bHQsIG1heFdhaXQgLSB0aW1lU2luY2VMYXN0SW52b2tlKSA6IHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIHNob3VsZEludm9rZSh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZTtcblxuICAgIC8vIEVpdGhlciB0aGlzIGlzIHRoZSBmaXJzdCBjYWxsLCBhY3Rpdml0eSBoYXMgc3RvcHBlZCBhbmQgd2UncmUgYXQgdGhlXG4gICAgLy8gdHJhaWxpbmcgZWRnZSwgdGhlIHN5c3RlbSB0aW1lIGhhcyBnb25lIGJhY2t3YXJkcyBhbmQgd2UncmUgdHJlYXRpbmdcbiAgICAvLyBpdCBhcyB0aGUgdHJhaWxpbmcgZWRnZSwgb3Igd2UndmUgaGl0IHRoZSBgbWF4V2FpdGAgbGltaXQuXG4gICAgcmV0dXJuIChsYXN0Q2FsbFRpbWUgPT09IHVuZGVmaW5lZCB8fCAodGltZVNpbmNlTGFzdENhbGwgPj0gd2FpdCkgfHxcbiAgICAgICh0aW1lU2luY2VMYXN0Q2FsbCA8IDApIHx8IChtYXhpbmcgJiYgdGltZVNpbmNlTGFzdEludm9rZSA+PSBtYXhXYWl0KSk7XG4gIH1cblxuICBmdW5jdGlvbiB0aW1lckV4cGlyZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKTtcbiAgICBpZiAoc2hvdWxkSW52b2tlKHRpbWUpKSB7XG4gICAgICByZXR1cm4gdHJhaWxpbmdFZGdlKHRpbWUpO1xuICAgIH1cbiAgICAvLyBSZXN0YXJ0IHRoZSB0aW1lci5cbiAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHJlbWFpbmluZ1dhaXQodGltZSkpO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJhaWxpbmdFZGdlKHRpbWUpIHtcbiAgICB0aW1lcklkID0gdW5kZWZpbmVkO1xuXG4gICAgLy8gT25seSBpbnZva2UgaWYgd2UgaGF2ZSBgbGFzdEFyZ3NgIHdoaWNoIG1lYW5zIGBmdW5jYCBoYXMgYmVlblxuICAgIC8vIGRlYm91bmNlZCBhdCBsZWFzdCBvbmNlLlxuICAgIGlmICh0cmFpbGluZyAmJiBsYXN0QXJncykge1xuICAgICAgcmV0dXJuIGludm9rZUZ1bmModGltZSk7XG4gICAgfVxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGNhbmNlbCgpIHtcbiAgICBpZiAodGltZXJJZCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZXJJZCk7XG4gICAgfVxuICAgIGxhc3RJbnZva2VUaW1lID0gMDtcbiAgICBsYXN0QXJncyA9IGxhc3RDYWxsVGltZSA9IGxhc3RUaGlzID0gdGltZXJJZCA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGZsdXNoKCkge1xuICAgIHJldHVybiB0aW1lcklkID09PSB1bmRlZmluZWQgPyByZXN1bHQgOiB0cmFpbGluZ0VkZ2Uobm93KCkpO1xuICB9XG5cbiAgZnVuY3Rpb24gZGVib3VuY2VkKCkge1xuICAgIHZhciB0aW1lID0gbm93KCksXG4gICAgICAgIGlzSW52b2tpbmcgPSBzaG91bGRJbnZva2UodGltZSk7XG5cbiAgICBsYXN0QXJncyA9IGFyZ3VtZW50cztcbiAgICBsYXN0VGhpcyA9IHRoaXM7XG4gICAgbGFzdENhbGxUaW1lID0gdGltZTtcblxuICAgIGlmIChpc0ludm9raW5nKSB7XG4gICAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBsZWFkaW5nRWRnZShsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgICAgaWYgKG1heGluZykge1xuICAgICAgICAvLyBIYW5kbGUgaW52b2NhdGlvbnMgaW4gYSB0aWdodCBsb29wLlxuICAgICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgICAgICByZXR1cm4gaW52b2tlRnVuYyhsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGRlYm91bmNlZC5jYW5jZWwgPSBjYW5jZWw7XG4gIGRlYm91bmNlZC5mbHVzaCA9IGZsdXNoO1xuICByZXR1cm4gZGVib3VuY2VkO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSB0aHJvdHRsZWQgZnVuY3Rpb24gdGhhdCBvbmx5IGludm9rZXMgYGZ1bmNgIGF0IG1vc3Qgb25jZSBwZXJcbiAqIGV2ZXJ5IGB3YWl0YCBtaWxsaXNlY29uZHMuIFRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgXG4gKiBtZXRob2QgdG8gY2FuY2VsIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvXG4gKiBpbW1lZGlhdGVseSBpbnZva2UgdGhlbS4gUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2BcbiAqIHNob3VsZCBiZSBpbnZva2VkIG9uIHRoZSBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGBcbiAqIHRpbWVvdXQuIFRoZSBgZnVuY2AgaXMgaW52b2tlZCB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGVcbiAqIHRocm90dGxlZCBmdW5jdGlvbi4gU3Vic2VxdWVudCBjYWxscyB0byB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uIHJldHVybiB0aGVcbiAqIHJlc3VsdCBvZiB0aGUgbGFzdCBgZnVuY2AgaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIHRocm90dGxlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy50aHJvdHRsZWAgYW5kIGBfLmRlYm91bmNlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIHRocm90dGxlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHRocm90dGxlIGludm9jYXRpb25zIHRvLlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHRocm90dGxlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgZXhjZXNzaXZlbHkgdXBkYXRpbmcgdGhlIHBvc2l0aW9uIHdoaWxlIHNjcm9sbGluZy5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdzY3JvbGwnLCBfLnRocm90dGxlKHVwZGF0ZVBvc2l0aW9uLCAxMDApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHJlbmV3VG9rZW5gIHdoZW4gdGhlIGNsaWNrIGV2ZW50IGlzIGZpcmVkLCBidXQgbm90IG1vcmUgdGhhbiBvbmNlIGV2ZXJ5IDUgbWludXRlcy5cbiAqIHZhciB0aHJvdHRsZWQgPSBfLnRocm90dGxlKHJlbmV3VG9rZW4sIDMwMDAwMCwgeyAndHJhaWxpbmcnOiBmYWxzZSB9KTtcbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCB0aHJvdHRsZWQpO1xuICpcbiAqIC8vIENhbmNlbCB0aGUgdHJhaWxpbmcgdGhyb3R0bGVkIGludm9jYXRpb24uXG4gKiBqUXVlcnkod2luZG93KS5vbigncG9wc3RhdGUnLCB0aHJvdHRsZWQuY2FuY2VsKTtcbiAqL1xuZnVuY3Rpb24gdGhyb3R0bGUoZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICB2YXIgbGVhZGluZyA9IHRydWUsXG4gICAgICB0cmFpbGluZyA9IHRydWU7XG5cbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICdsZWFkaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLmxlYWRpbmcgOiBsZWFkaW5nO1xuICAgIHRyYWlsaW5nID0gJ3RyYWlsaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLnRyYWlsaW5nIDogdHJhaWxpbmc7XG4gIH1cbiAgcmV0dXJuIGRlYm91bmNlKGZ1bmMsIHdhaXQsIHtcbiAgICAnbGVhZGluZyc6IGxlYWRpbmcsXG4gICAgJ21heFdhaXQnOiB3YWl0LFxuICAgICd0cmFpbGluZyc6IHRyYWlsaW5nXG4gIH0pO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIHRoZVxuICogW2xhbmd1YWdlIHR5cGVdKGh0dHA6Ly93d3cuZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi83LjAvI3NlYy1lY21hc2NyaXB0LWxhbmd1YWdlLXR5cGVzKVxuICogb2YgYE9iamVjdGAuIChlLmcuIGFycmF5cywgZnVuY3Rpb25zLCBvYmplY3RzLCByZWdleGVzLCBgbmV3IE51bWJlcigwKWAsIGFuZCBgbmV3IFN0cmluZygnJylgKVxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGFuIG9iamVjdCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0KHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChfLm5vb3ApO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QobnVsbCk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc09iamVjdCh2YWx1ZSkge1xuICB2YXIgdHlwZSA9IHR5cGVvZiB2YWx1ZTtcbiAgcmV0dXJuICEhdmFsdWUgJiYgKHR5cGUgPT0gJ29iamVjdCcgfHwgdHlwZSA9PSAnZnVuY3Rpb24nKTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZS4gQSB2YWx1ZSBpcyBvYmplY3QtbGlrZSBpZiBpdCdzIG5vdCBgbnVsbGBcbiAqIGFuZCBoYXMgYSBgdHlwZW9mYCByZXN1bHQgb2YgXCJvYmplY3RcIi5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZSwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZSh7fSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdExpa2UoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShfLm5vb3ApO1xuICogLy8gPT4gZmFsc2VcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0TGlrZSh2YWx1ZSkge1xuICByZXR1cm4gISF2YWx1ZSAmJiB0eXBlb2YgdmFsdWUgPT0gJ29iamVjdCc7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhIGBTeW1ib2xgIHByaW1pdGl2ZSBvciBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYSBzeW1ib2wsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc1N5bWJvbChTeW1ib2wuaXRlcmF0b3IpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNTeW1ib2woJ2FiYycpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNTeW1ib2wodmFsdWUpIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PSAnc3ltYm9sJyB8fFxuICAgIChpc09iamVjdExpa2UodmFsdWUpICYmIG9iamVjdFRvU3RyaW5nLmNhbGwodmFsdWUpID09IHN5bWJvbFRhZyk7XG59XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBhIG51bWJlci5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gcHJvY2Vzcy5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIG51bWJlci5cbiAqIEBleGFtcGxlXG4gKlxuICogXy50b051bWJlcigzLjIpO1xuICogLy8gPT4gMy4yXG4gKlxuICogXy50b051bWJlcihOdW1iZXIuTUlOX1ZBTFVFKTtcbiAqIC8vID0+IDVlLTMyNFxuICpcbiAqIF8udG9OdW1iZXIoSW5maW5pdHkpO1xuICogLy8gPT4gSW5maW5pdHlcbiAqXG4gKiBfLnRvTnVtYmVyKCczLjInKTtcbiAqIC8vID0+IDMuMlxuICovXG5mdW5jdGlvbiB0b051bWJlcih2YWx1ZSkge1xuICBpZiAodHlwZW9mIHZhbHVlID09ICdudW1iZXInKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIGlmIChpc1N5bWJvbCh2YWx1ZSkpIHtcbiAgICByZXR1cm4gTkFOO1xuICB9XG4gIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICB2YXIgb3RoZXIgPSB0eXBlb2YgdmFsdWUudmFsdWVPZiA9PSAnZnVuY3Rpb24nID8gdmFsdWUudmFsdWVPZigpIDogdmFsdWU7XG4gICAgdmFsdWUgPSBpc09iamVjdChvdGhlcikgPyAob3RoZXIgKyAnJykgOiBvdGhlcjtcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlID09PSAwID8gdmFsdWUgOiArdmFsdWU7XG4gIH1cbiAgdmFsdWUgPSB2YWx1ZS5yZXBsYWNlKHJlVHJpbSwgJycpO1xuICB2YXIgaXNCaW5hcnkgPSByZUlzQmluYXJ5LnRlc3QodmFsdWUpO1xuICByZXR1cm4gKGlzQmluYXJ5IHx8IHJlSXNPY3RhbC50ZXN0KHZhbHVlKSlcbiAgICA/IGZyZWVQYXJzZUludCh2YWx1ZS5zbGljZSgyKSwgaXNCaW5hcnkgPyAyIDogOClcbiAgICA6IChyZUlzQmFkSGV4LnRlc3QodmFsdWUpID8gTkFOIDogK3ZhbHVlKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB0aHJvdHRsZTtcbiIsInZhciBnO1xyXG5cclxuLy8gVGhpcyB3b3JrcyBpbiBub24tc3RyaWN0IG1vZGVcclxuZyA9IChmdW5jdGlvbigpIHtcclxuXHRyZXR1cm4gdGhpcztcclxufSkoKTtcclxuXHJcbnRyeSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiBldmFsIGlzIGFsbG93ZWQgKHNlZSBDU1ApXHJcblx0ZyA9IGcgfHwgRnVuY3Rpb24oXCJyZXR1cm4gdGhpc1wiKSgpIHx8ICgxLCBldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2ggKGUpIHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIHRoZSB3aW5kb3cgcmVmZXJlbmNlIGlzIGF2YWlsYWJsZVxyXG5cdGlmICh0eXBlb2Ygd2luZG93ID09PSBcIm9iamVjdFwiKSBnID0gd2luZG93O1xyXG59XHJcblxyXG4vLyBnIGNhbiBzdGlsbCBiZSB1bmRlZmluZWQsIGJ1dCBub3RoaW5nIHRvIGRvIGFib3V0IGl0Li4uXHJcbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXHJcbi8vIGVhc2llciB0byBoYW5kbGUgdGhpcyBjYXNlLiBpZighZ2xvYmFsKSB7IC4uLn1cclxuXHJcbm1vZHVsZS5leHBvcnRzID0gZztcclxuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5pbXBvcnQgeyBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcblxuZXhwb3J0IGNsYXNzIEFjdGl2ZUxpbmVNYXJrZXIge1xuXHRwcml2YXRlIF9jdXJyZW50OiBhbnk7XG5cblx0b25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmU6IG51bWJlcikge1xuXHRcdGNvbnN0IHsgcHJldmlvdXMgfSA9IGdldEVsZW1lbnRzRm9yU291cmNlTGluZShsaW5lKTtcblx0XHR0aGlzLl91cGRhdGUocHJldmlvdXMgJiYgcHJldmlvdXMuZWxlbWVudCk7XG5cdH1cblxuXHRfdXBkYXRlKGJlZm9yZTogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHR0aGlzLl91bm1hcmtBY3RpdmVFbGVtZW50KHRoaXMuX2N1cnJlbnQpO1xuXHRcdHRoaXMuX21hcmtBY3RpdmVFbGVtZW50KGJlZm9yZSk7XG5cdFx0dGhpcy5fY3VycmVudCA9IGJlZm9yZTtcblx0fVxuXG5cdF91bm1hcmtBY3RpdmVFbGVtZW50KGVsZW1lbnQ6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkKSB7XG5cdFx0aWYgKCFlbGVtZW50KSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdGVsZW1lbnQuY2xhc3NOYW1lID0gZWxlbWVudC5jbGFzc05hbWUucmVwbGFjZSgvXFxiY29kZS1hY3RpdmUtbGluZVxcYi9nLCAnJyk7XG5cdH1cblxuXHRfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudDogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHRpZiAoIWVsZW1lbnQpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0ZWxlbWVudC5jbGFzc05hbWUgKz0gJyBjb2RlLWFjdGl2ZS1saW5lJztcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGY6ICgpID0+IHZvaWQpIHtcblx0aWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdsb2FkaW5nJyB8fCBkb2N1bWVudC5yZWFkeVN0YXRlIGFzIHN0cmluZyA9PT0gJ3VuaW5pdGlhbGl6ZWQnKSB7XG5cdFx0ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGYpO1xuXHR9IGVsc2Uge1xuXHRcdGYoKTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBBY3RpdmVMaW5lTWFya2VyIH0gZnJvbSAnLi9hY3RpdmVMaW5lTWFya2VyJztcbmltcG9ydCB7IG9uY2VEb2N1bWVudExvYWRlZCB9IGZyb20gJy4vZXZlbnRzJztcbmltcG9ydCB7IGNyZWF0ZVBvc3RlckZvclZzQ29kZSB9IGZyb20gJy4vbWVzc2FnaW5nJztcbmltcG9ydCB7IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0LCBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcbmltcG9ydCB7IGdldFNldHRpbmdzLCBnZXREYXRhIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5pbXBvcnQgdGhyb3R0bGUgPSByZXF1aXJlKCdsb2Rhc2gudGhyb3R0bGUnKTtcblxuZGVjbGFyZSB2YXIgYWNxdWlyZVZzQ29kZUFwaTogYW55O1xuXG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IEFjdGl2ZUxpbmVNYXJrZXIoKTtcbmNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuY29uc3QgdnNjb2RlID0gYWNxdWlyZVZzQ29kZUFwaSgpO1xuXG4vLyBTZXQgVlMgQ29kZSBzdGF0ZVxubGV0IHN0YXRlID0gZ2V0RGF0YTx7IGxpbmU6IG51bWJlciB9PignZGF0YS1zdGF0ZScpO1xudnNjb2RlLnNldFN0YXRlKHN0YXRlKTtcblxuY29uc3QgbWVzc2FnaW5nID0gY3JlYXRlUG9zdGVyRm9yVnNDb2RlKHZzY29kZSk7XG5cbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG5cbndpbmRvdy5vbmxvYWQgPSAoKSA9PiB7XG5cdHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5cbm9uY2VEb2N1bWVudExvYWRlZCgoKSA9PiB7XG5cdGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuXHRcdHNldFRpbWVvdXQoKCkgPT4ge1xuXHRcdFx0Y29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcblx0XHRcdGlmICghaXNOYU4oaW5pdGlhbExpbmUpKSB7XG5cdFx0XHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRcdFx0c2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcblx0XHRcdH1cblx0XHR9LCAwKTtcblx0fVxufSk7XG5cbmNvbnN0IG9uVXBkYXRlVmlldyA9ICgoKSA9PiB7XG5cdGNvbnN0IGRvU2Nyb2xsID0gdGhyb3R0bGUoKGxpbmU6IG51bWJlcikgPT4ge1xuXHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG5cdH0sIDUwKTtcblxuXHRyZXR1cm4gKGxpbmU6IG51bWJlciwgc2V0dGluZ3M6IGFueSkgPT4ge1xuXHRcdGlmICghaXNOYU4obGluZSkpIHtcblx0XHRcdHNldHRpbmdzLmxpbmUgPSBsaW5lO1xuXHRcdFx0ZG9TY3JvbGwobGluZSk7XG5cdFx0fVxuXHR9O1xufSkoKTtcblxubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG5cdGNvbnN0IGltYWdlSW5mbzogeyBpZDogc3RyaW5nLCBoZWlnaHQ6IG51bWJlciwgd2lkdGg6IG51bWJlciB9W10gPSBbXTtcblx0bGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcblx0aWYgKGltYWdlcykge1xuXHRcdGxldCBpO1xuXHRcdGZvciAoaSA9IDA7IGkgPCBpbWFnZXMubGVuZ3RoOyBpKyspIHtcblx0XHRcdGNvbnN0IGltZyA9IGltYWdlc1tpXTtcblxuXHRcdFx0aWYgKGltZy5jbGFzc0xpc3QuY29udGFpbnMoJ2xvYWRpbmcnKSkge1xuXHRcdFx0XHRpbWcuY2xhc3NMaXN0LnJlbW92ZSgnbG9hZGluZycpO1xuXHRcdFx0fVxuXG5cdFx0XHRpbWFnZUluZm8ucHVzaCh7XG5cdFx0XHRcdGlkOiBpbWcuaWQsXG5cdFx0XHRcdGhlaWdodDogaW1nLmhlaWdodCxcblx0XHRcdFx0d2lkdGg6IGltZy53aWR0aFxuXHRcdFx0fSk7XG5cdFx0fVxuXG5cdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdjYWNoZUltYWdlU2l6ZXMnLCBpbWFnZUluZm8pO1xuXHR9XG59LCA1MCk7XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdyZXNpemUnLCAoKSA9PiB7XG5cdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0dXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgZXZlbnQgPT4ge1xuXHRpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuXHRcdHJldHVybjtcblx0fVxuXG5cdHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG5cdFx0Y2FzZSAnb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uJzpcblx0XHRcdG1hcmtlci5vbkRpZENoYW5nZVRleHRFZGl0b3JTZWxlY3Rpb24oZXZlbnQuZGF0YS5saW5lKTtcblx0XHRcdGJyZWFrO1xuXG5cdFx0Y2FzZSAndXBkYXRlVmlldyc6XG5cdFx0XHRvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG5cdFx0XHRicmVhaztcblx0fVxufSwgZmFsc2UpO1xuXG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcblx0aWYgKCFzZXR0aW5ncy5kb3VibGVDbGlja1RvU3dpdGNoVG9FZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHQvLyBJZ25vcmUgY2xpY2tzIG9uIGxpbmtzXG5cdGZvciAobGV0IG5vZGUgPSBldmVudC50YXJnZXQgYXMgSFRNTEVsZW1lbnQ7IG5vZGU7IG5vZGUgPSBub2RlLnBhcmVudE5vZGUgYXMgSFRNTEVsZW1lbnQpIHtcblx0XHRpZiAobm9kZS50YWdOYW1lID09PSAnQScpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdH1cblxuXHRjb25zdCBvZmZzZXQgPSBldmVudC5wYWdlWTtcblx0Y29uc3QgbGluZSA9IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmICh0eXBlb2YgbGluZSA9PT0gJ251bWJlcicgJiYgIWlzTmFOKGxpbmUpKSB7XG5cdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcblx0fVxufSk7XG5cbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgZXZlbnQgPT4ge1xuXHRpZiAoIWV2ZW50KSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0bGV0IG5vZGU6IGFueSA9IGV2ZW50LnRhcmdldDtcblx0d2hpbGUgKG5vZGUpIHtcblx0XHRpZiAobm9kZS50YWdOYW1lICYmIG5vZGUudGFnTmFtZSA9PT0gJ0EnICYmIG5vZGUuaHJlZikge1xuXHRcdFx0aWYgKG5vZGUuZ2V0QXR0cmlidXRlKCdocmVmJykuc3RhcnRzV2l0aCgnIycpKSB7XG5cdFx0XHRcdGJyZWFrO1xuXHRcdFx0fVxuXHRcdFx0aWYgKG5vZGUuaHJlZi5zdGFydHNXaXRoKCdmaWxlOi8vJykgfHwgbm9kZS5ocmVmLnN0YXJ0c1dpdGgoJ3ZzY29kZS1yZXNvdXJjZTonKSB8fCBub2RlLmhyZWYuc3RhcnRzV2l0aChzZXR0aW5ncy53ZWJ2aWV3UmVzb3VyY2VSb290KSkge1xuXHRcdFx0XHRjb25zdCBbcGF0aCwgZnJhZ21lbnRdID0gbm9kZS5ocmVmLnJlcGxhY2UoL14oZmlsZTpcXC9cXC98dnNjb2RlLXJlc291cmNlOikvaSwgJycpLnJlcGxhY2UobmV3IFJlZ0V4cChgXiR7ZXNjYXBlUmVnRXhwKHNldHRpbmdzLndlYnZpZXdSZXNvdXJjZVJvb3QpfWApKS5zcGxpdCgnIycpO1xuXHRcdFx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NsaWNrTGluaycsIHsgcGF0aCwgZnJhZ21lbnQgfSk7XG5cdFx0XHRcdGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG5cdFx0XHRcdGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuXHRcdFx0XHRicmVhaztcblx0XHRcdH1cblx0XHRcdGJyZWFrO1xuXHRcdH1cblx0XHRub2RlID0gbm9kZS5wYXJlbnROb2RlO1xuXHR9XG59LCB0cnVlKTtcblxuaWYgKHNldHRpbmdzLnNjcm9sbEVkaXRvcldpdGhQcmV2aWV3KSB7XG5cdHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdzY3JvbGwnLCB0aHJvdHRsZSgoKSA9PiB7XG5cdFx0aWYgKHNjcm9sbERpc2FibGVkKSB7XG5cdFx0XHRzY3JvbGxEaXNhYmxlZCA9IGZhbHNlO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRjb25zdCBsaW5lID0gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQod2luZG93LnNjcm9sbFkpO1xuXHRcdFx0aWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcblx0XHRcdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdyZXZlYWxMaW5lJywgeyBsaW5lIH0pO1xuXHRcdFx0XHRzdGF0ZS5saW5lID0gbGluZTtcblx0XHRcdFx0dnNjb2RlLnNldFN0YXRlKHN0YXRlKTtcblx0XHRcdH1cblx0XHR9XG5cdH0sIDUwKSk7XG59XG5cbmZ1bmN0aW9uIGVzY2FwZVJlZ0V4cCh0ZXh0OiBzdHJpbmcpIHtcblx0cmV0dXJuIHRleHQucmVwbGFjZSgvWy1bXFxde30oKSorPy4sXFxcXF4kfCNcXHNdL2csICdcXFxcJCYnKTtcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgZ2V0U2V0dGluZ3MgfSBmcm9tICcuL3NldHRpbmdzJztcblxuZXhwb3J0IGludGVyZmFjZSBNZXNzYWdlUG9zdGVyIHtcblx0LyoqXG5cdCAqIFBvc3QgYSBtZXNzYWdlIHRvIHRoZSBtYXJrZG93biBleHRlbnNpb25cblx0ICovXG5cdHBvc3RNZXNzYWdlKHR5cGU6IHN0cmluZywgYm9keTogb2JqZWN0KTogdm9pZDtcbn1cblxuZXhwb3J0IGNvbnN0IGNyZWF0ZVBvc3RlckZvclZzQ29kZSA9ICh2c2NvZGU6IGFueSkgPT4ge1xuXHRyZXR1cm4gbmV3IGNsYXNzIGltcGxlbWVudHMgTWVzc2FnZVBvc3RlciB7XG5cdFx0cG9zdE1lc3NhZ2UodHlwZTogc3RyaW5nLCBib2R5OiBvYmplY3QpOiB2b2lkIHtcblx0XHRcdHZzY29kZS5wb3N0TWVzc2FnZSh7XG5cdFx0XHRcdHR5cGUsXG5cdFx0XHRcdHNvdXJjZTogZ2V0U2V0dGluZ3MoKS5zb3VyY2UsXG5cdFx0XHRcdGJvZHlcblx0XHRcdH0pO1xuXHRcdH1cblx0fTtcbn07XG5cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBnZXRTZXR0aW5ncyB9IGZyb20gJy4vc2V0dGluZ3MnO1xuXG5cbmZ1bmN0aW9uIGNsYW1wKG1pbjogbnVtYmVyLCBtYXg6IG51bWJlciwgdmFsdWU6IG51bWJlcikge1xuXHRyZXR1cm4gTWF0aC5taW4obWF4LCBNYXRoLm1heChtaW4sIHZhbHVlKSk7XG59XG5cbmZ1bmN0aW9uIGNsYW1wTGluZShsaW5lOiBudW1iZXIpIHtcblx0cmV0dXJuIGNsYW1wKDAsIGdldFNldHRpbmdzKCkubGluZUNvdW50IC0gMSwgbGluZSk7XG59XG5cblxuZXhwb3J0IGludGVyZmFjZSBDb2RlTGluZUVsZW1lbnQge1xuXHRlbGVtZW50OiBIVE1MRWxlbWVudDtcblx0bGluZTogbnVtYmVyO1xufVxuXG5jb25zdCBnZXRDb2RlTGluZUVsZW1lbnRzID0gKCgpID0+IHtcblx0bGV0IGVsZW1lbnRzOiBDb2RlTGluZUVsZW1lbnRbXTtcblx0cmV0dXJuICgpID0+IHtcblx0XHRpZiAoIWVsZW1lbnRzKSB7XG5cdFx0XHRlbGVtZW50cyA9IFt7IGVsZW1lbnQ6IGRvY3VtZW50LmJvZHksIGxpbmU6IDAgfV07XG5cdFx0XHRmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS1saW5lJykpIHtcblx0XHRcdFx0Y29uc3QgbGluZSA9ICtlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS1saW5lJykhO1xuXHRcdFx0XHRpZiAoIWlzTmFOKGxpbmUpKSB7XG5cdFx0XHRcdFx0ZWxlbWVudHMucHVzaCh7IGVsZW1lbnQ6IGVsZW1lbnQgYXMgSFRNTEVsZW1lbnQsIGxpbmUgfSk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdFx0cmV0dXJuIGVsZW1lbnRzO1xuXHR9O1xufSkoKTtcblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgbWFwIHRvIGEgc3BlY2lmaWMgdGFyZ2V0IGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqXG4gKiBJZiBhbiBleGFjdCBtYXRjaCwgcmV0dXJucyBhIHNpbmdsZSBlbGVtZW50LiBJZiB0aGUgbGluZSBpcyBiZXR3ZWVuIGVsZW1lbnRzLFxuICogcmV0dXJucyB0aGUgZWxlbWVudCBwcmlvciB0byBhbmQgdGhlIGVsZW1lbnQgYWZ0ZXIgdGhlIGdpdmVuIGxpbmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUodGFyZ2V0TGluZTogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZU51bWJlciA9IE1hdGguZmxvb3IodGFyZ2V0TGluZSk7XG5cdGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuXHRsZXQgcHJldmlvdXMgPSBsaW5lc1swXSB8fCBudWxsO1xuXHRmb3IgKGNvbnN0IGVudHJ5IG9mIGxpbmVzKSB7XG5cdFx0aWYgKGVudHJ5LmxpbmUgPT09IGxpbmVOdW1iZXIpIHtcblx0XHRcdHJldHVybiB7IHByZXZpb3VzOiBlbnRyeSwgbmV4dDogdW5kZWZpbmVkIH07XG5cdFx0fSBlbHNlIGlmIChlbnRyeS5saW5lID4gbGluZU51bWJlcikge1xuXHRcdFx0cmV0dXJuIHsgcHJldmlvdXMsIG5leHQ6IGVudHJ5IH07XG5cdFx0fVxuXHRcdHByZXZpb3VzID0gZW50cnk7XG5cdH1cblx0cmV0dXJuIHsgcHJldmlvdXMgfTtcbn1cblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgYXJlIGF0IGEgc3BlY2lmaWMgcGl4ZWwgb2Zmc2V0IG9uIHRoZSBwYWdlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldDogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG5cdGNvbnN0IHBvc2l0aW9uID0gb2Zmc2V0IC0gd2luZG93LnNjcm9sbFk7XG5cdGxldCBsbyA9IC0xO1xuXHRsZXQgaGkgPSBsaW5lcy5sZW5ndGggLSAxO1xuXHR3aGlsZSAobG8gKyAxIDwgaGkpIHtcblx0XHRjb25zdCBtaWQgPSBNYXRoLmZsb29yKChsbyArIGhpKSAvIDIpO1xuXHRcdGNvbnN0IGJvdW5kcyA9IGxpbmVzW21pZF0uZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRpZiAoYm91bmRzLnRvcCArIGJvdW5kcy5oZWlnaHQgPj0gcG9zaXRpb24pIHtcblx0XHRcdGhpID0gbWlkO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGxvID0gbWlkO1xuXHRcdH1cblx0fVxuXHRjb25zdCBoaUVsZW1lbnQgPSBsaW5lc1toaV07XG5cdGNvbnN0IGhpQm91bmRzID0gaGlFbGVtZW50LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cdGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG5cdFx0Y29uc3QgbG9FbGVtZW50ID0gbGluZXNbbG9dO1xuXHRcdHJldHVybiB7IHByZXZpb3VzOiBsb0VsZW1lbnQsIG5leHQ6IGhpRWxlbWVudCB9O1xuXHR9XG5cdHJldHVybiB7IHByZXZpb3VzOiBoaUVsZW1lbnQgfTtcbn1cblxuLyoqXG4gKiBBdHRlbXB0IHRvIHJldmVhbCB0aGUgZWxlbWVudCBmb3IgYSBzb3VyY2UgbGluZSBpbiB0aGUgZWRpdG9yLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGxpbmU6IG51bWJlcikge1xuXHRpZiAoIWdldFNldHRpbmdzKCkuc2Nyb2xsUHJldmlld1dpdGhFZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHRpZiAobGluZSA8PSAwKSB7XG5cdFx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgMCk7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKGxpbmUpO1xuXHRpZiAoIXByZXZpb3VzKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cdGxldCBzY3JvbGxUbyA9IDA7XG5cdGNvbnN0IHJlY3QgPSBwcmV2aW91cy5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuXHRjb25zdCBwcmV2aW91c1RvcCA9IHJlY3QudG9wO1xuXHRpZiAobmV4dCAmJiBuZXh0LmxpbmUgIT09IHByZXZpb3VzLmxpbmUpIHtcblx0XHQvLyBCZXR3ZWVuIHR3byBlbGVtZW50cy4gR28gdG8gcGVyY2VudGFnZSBvZmZzZXQgYmV0d2VlbiB0aGVtLlxuXHRcdGNvbnN0IGJldHdlZW5Qcm9ncmVzcyA9IChsaW5lIC0gcHJldmlvdXMubGluZSkgLyAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0Y29uc3QgZWxlbWVudE9mZnNldCA9IG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c1RvcDtcblx0XHRzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgYmV0d2VlblByb2dyZXNzICogZWxlbWVudE9mZnNldDtcblx0fSBlbHNlIHtcblx0XHRjb25zdCBwcm9ncmVzc0luRWxlbWVudCA9IGxpbmUgLSBNYXRoLmZsb29yKGxpbmUpO1xuXHRcdHNjcm9sbFRvID0gcHJldmlvdXNUb3AgKyAocmVjdC5oZWlnaHQgKiBwcm9ncmVzc0luRWxlbWVudCk7XG5cdH1cblx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgTWF0aC5tYXgoMSwgd2luZG93LnNjcm9sbFkgKyBzY3JvbGxUbykpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0OiBudW1iZXIpIHtcblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmIChwcmV2aW91cykge1xuXHRcdGNvbnN0IHByZXZpb3VzQm91bmRzID0gcHJldmlvdXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRjb25zdCBvZmZzZXRGcm9tUHJldmlvdXMgPSAob2Zmc2V0IC0gd2luZG93LnNjcm9sbFkgLSBwcmV2aW91c0JvdW5kcy50b3ApO1xuXHRcdGlmIChuZXh0KSB7XG5cdFx0XHRjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcblx0XHRcdGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgKiAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGNvbnN0IHByb2dyZXNzV2l0aGluRWxlbWVudCA9IG9mZnNldEZyb21QcmV2aW91cyAvIChwcmV2aW91c0JvdW5kcy5oZWlnaHQpO1xuXHRcdFx0Y29uc3QgbGluZSA9IHByZXZpb3VzLmxpbmUgKyBwcm9ncmVzc1dpdGhpbkVsZW1lbnQ7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0fVxuXHRyZXR1cm4gbnVsbDtcbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgaW50ZXJmYWNlIFByZXZpZXdTZXR0aW5ncyB7XG5cdHJlYWRvbmx5IHNvdXJjZTogc3RyaW5nO1xuXHRyZWFkb25seSBsaW5lOiBudW1iZXI7XG5cdHJlYWRvbmx5IGxpbmVDb3VudDogbnVtYmVyO1xuXHRyZWFkb25seSBzY3JvbGxQcmV2aWV3V2l0aEVkaXRvcj86IGJvb2xlYW47XG5cdHJlYWRvbmx5IHNjcm9sbEVkaXRvcldpdGhQcmV2aWV3OiBib29sZWFuO1xuXHRyZWFkb25seSBkaXNhYmxlU2VjdXJpdHlXYXJuaW5nczogYm9vbGVhbjtcblx0cmVhZG9ubHkgZG91YmxlQ2xpY2tUb1N3aXRjaFRvRWRpdG9yOiBib29sZWFuO1xuXHRyZWFkb25seSB3ZWJ2aWV3UmVzb3VyY2VSb290OiBzdHJpbmc7XG59XG5cbmxldCBjYWNoZWRTZXR0aW5nczogUHJldmlld1NldHRpbmdzIHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGF0YTxUID0ge30+KGtleTogc3RyaW5nKTogVCB7XG5cdGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuXHRpZiAoZWxlbWVudCkge1xuXHRcdGNvbnN0IGRhdGEgPSBlbGVtZW50LmdldEF0dHJpYnV0ZShrZXkpO1xuXHRcdGlmIChkYXRhKSB7XG5cdFx0XHRyZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcblx0XHR9XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKTogUHJldmlld1NldHRpbmdzIHtcblx0aWYgKGNhY2hlZFNldHRpbmdzKSB7XG5cdFx0cmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuXHR9XG5cblx0Y2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbiJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file diff --git a/extensions/markdown-language-features/media/pre.js b/extensions/markdown-language-features/media/pre.js index f0d83635dd0..85e9c264915 100644 --- a/extensions/markdown-language-features/media/pre.js +++ b/extensions/markdown-language-features/media/pre.js @@ -277,4 +277,4 @@ exports.getStrings = getStrings; /***/ }) /******/ }); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvY3NwLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2xvYWRpbmcudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvcHJlLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3NldHRpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3N0cmluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0EseURBQWlELGNBQWM7QUFDL0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsbUNBQTJCLDBCQUEwQixFQUFFO0FBQ3ZELHlDQUFpQyxlQUFlO0FBQ2hEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDhEQUFzRCwrREFBK0Q7O0FBRXJIO0FBQ0E7OztBQUdBO0FBQ0E7Ozs7Ozs7Ozs7Ozs7O0FDbkVBOzs7Z0dBR2dHOztBQUdoRyxzRkFBeUM7QUFDekMsbUZBQXVDO0FBRXZDOztHQUVHO0FBQ0gsTUFBYSxVQUFVO0lBTXRCO1FBTFEsWUFBTyxHQUFHLEtBQUssQ0FBQztRQUNoQixzQkFBaUIsR0FBRyxLQUFLLENBQUM7UUFLakMsUUFBUSxDQUFDLGdCQUFnQixDQUFDLHlCQUF5QixFQUFFLEdBQUcsRUFBRTtZQUN6RCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDckIsQ0FBQyxDQUFDLENBQUM7UUFFSCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDNUMsSUFBSSxLQUFLLElBQUksS0FBSyxDQUFDLElBQUksSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxzQkFBc0IsRUFBRTtnQkFDdEUsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO2FBQ3BCO1FBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU0sU0FBUyxDQUFDLE1BQXFCO1FBQ3JDLElBQUksQ0FBQyxTQUFTLEdBQUcsTUFBTSxDQUFDO1FBQ3hCLElBQUksSUFBSSxDQUFDLGlCQUFpQixFQUFFO1lBQzNCLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztTQUN0QjtJQUNGLENBQUM7SUFFTyxZQUFZO1FBQ25CLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7UUFDOUIsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBQ3ZCLENBQUM7SUFFTyxjQUFjO1FBQ3JCLE1BQU0sT0FBTyxHQUFHLG9CQUFVLEVBQUUsQ0FBQztRQUM3QixNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7UUFFL0IsSUFBSSxJQUFJLENBQUMsT0FBTyxJQUFJLFFBQVEsQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUU7WUFDeEUsT0FBTztTQUNQO1FBQ0QsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFFcEIsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNqRCxZQUFZLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQztRQUNyRCxZQUFZLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1FBQ3BELFlBQVksQ0FBQyxZQUFZLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBRWpFLFlBQVksQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzVDLFlBQVksQ0FBQyxZQUFZLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQ3RFLFlBQVksQ0FBQyxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQzNCLElBQUksQ0FBQyxTQUFVLENBQUMsV0FBVyxDQUFDLDZCQUE2QixFQUFFLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQ3pGLENBQUMsQ0FBQztRQUNGLFFBQVEsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ3pDLENBQUM7Q0FDRDtBQW5ERCxnQ0FtREM7Ozs7Ozs7Ozs7Ozs7OztBQ3pERCxNQUFhLG1CQUFtQjtJQU0vQjtRQUxRLG1CQUFjLEdBQWEsRUFBRSxDQUFDO1FBQzlCLG9CQUFlLEdBQVksS0FBSyxDQUFDO1FBS3hDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxLQUFVLEVBQUUsRUFBRTtZQUN2QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7WUFDM0MsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEMsQ0FBQyxDQUFDO1FBRUYsTUFBTSxDQUFDLGdCQUFnQixDQUFDLGtCQUFrQixFQUFFLEdBQUcsRUFBRTtZQUNoRCxLQUFLLE1BQU0sSUFBSSxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxpQkFBaUIsQ0FBa0MsRUFBRTtnQkFDdkcsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRTtvQkFDeEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxnQkFBZ0IsQ0FBQztpQkFDaEM7YUFDRDtRQUNGLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUU7WUFDcEMsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFO2dCQUNoQyxPQUFPO2FBQ1A7WUFDRCxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztZQUM1QixJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUU7Z0JBQ2hCLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO2FBQzFGO1FBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU0sU0FBUyxDQUFDLE1BQXFCO1FBQ3JDLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRTtZQUN6QixNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO1NBQ3JGO0lBQ0YsQ0FBQztDQUNEO0FBckNELGtEQXFDQzs7Ozs7Ozs7Ozs7Ozs7QUMzQ0Q7OztnR0FHZ0c7O0FBRWhHLHVFQUFtQztBQUNuQyxtRkFBZ0Q7QUFTaEQsTUFBTSxDQUFDLFVBQVUsR0FBRyxJQUFJLGdCQUFVLEVBQUUsQ0FBQztBQUNyQyxNQUFNLENBQUMsbUJBQW1CLEdBQUcsSUFBSSw2QkFBbUIsRUFBRSxDQUFDOzs7Ozs7Ozs7Ozs7OztBQ2hCdkQ7OztnR0FHZ0c7O0FBWWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFDLEdBQVc7SUFDbEMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0M7Ozs7Ozs7Ozs7Ozs7O0FDeENEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixVQUFVO0lBQ3pCLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxjQUFjLENBQUMsOEJBQThCLENBQUMsQ0FBQztJQUN0RSxJQUFJLEtBQUssRUFBRTtRQUNWLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDaEQsSUFBSSxJQUFJLEVBQUU7WUFDVCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDeEI7S0FDRDtJQUNELE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztBQUMzQyxDQUFDO0FBVEQsZ0NBU0MiLCJmaWxlIjoicHJlLmpzIiwic291cmNlc0NvbnRlbnQiOlsiIFx0Ly8gVGhlIG1vZHVsZSBjYWNoZVxuIFx0dmFyIGluc3RhbGxlZE1vZHVsZXMgPSB7fTtcblxuIFx0Ly8gVGhlIHJlcXVpcmUgZnVuY3Rpb25cbiBcdGZ1bmN0aW9uIF9fd2VicGFja19yZXF1aXJlX18obW9kdWxlSWQpIHtcblxuIFx0XHQvLyBDaGVjayBpZiBtb2R1bGUgaXMgaW4gY2FjaGVcbiBcdFx0aWYoaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0pIHtcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcbiBcdFx0fVxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0aTogbW9kdWxlSWQsXG4gXHRcdFx0bDogZmFsc2UsXG4gXHRcdFx0ZXhwb3J0czoge31cbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubCA9IHRydWU7XG5cbiBcdFx0Ly8gUmV0dXJuIHRoZSBleHBvcnRzIG9mIHRoZSBtb2R1bGVcbiBcdFx0cmV0dXJuIG1vZHVsZS5leHBvcnRzO1xuIFx0fVxuXG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlcyBvYmplY3QgKF9fd2VicGFja19tb2R1bGVzX18pXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm0gPSBtb2R1bGVzO1xuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZSBjYWNoZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5jID0gaW5zdGFsbGVkTW9kdWxlcztcblxuIFx0Ly8gZGVmaW5lIGdldHRlciBmdW5jdGlvbiBmb3IgaGFybW9ueSBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQgPSBmdW5jdGlvbihleHBvcnRzLCBuYW1lLCBnZXR0ZXIpIHtcbiBcdFx0aWYoIV9fd2VicGFja19yZXF1aXJlX18ubyhleHBvcnRzLCBuYW1lKSkge1xuIFx0XHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBuYW1lLCB7XG4gXHRcdFx0XHRjb25maWd1cmFibGU6IGZhbHNlLFxuIFx0XHRcdFx0ZW51bWVyYWJsZTogdHJ1ZSxcbiBcdFx0XHRcdGdldDogZ2V0dGVyXG4gXHRcdFx0fSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGdldERlZmF1bHRFeHBvcnQgZnVuY3Rpb24gZm9yIGNvbXBhdGliaWxpdHkgd2l0aCBub24taGFybW9ueSBtb2R1bGVzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm4gPSBmdW5jdGlvbihtb2R1bGUpIHtcbiBcdFx0dmFyIGdldHRlciA9IG1vZHVsZSAmJiBtb2R1bGUuX19lc01vZHVsZSA/XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0RGVmYXVsdCgpIHsgcmV0dXJuIG1vZHVsZVsnZGVmYXVsdCddOyB9IDpcbiBcdFx0XHRmdW5jdGlvbiBnZXRNb2R1bGVFeHBvcnRzKCkgeyByZXR1cm4gbW9kdWxlOyB9O1xuIFx0XHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQoZ2V0dGVyLCAnYScsIGdldHRlcik7XG4gXHRcdHJldHVybiBnZXR0ZXI7XG4gXHR9O1xuXG4gXHQvLyBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGxcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubyA9IGZ1bmN0aW9uKG9iamVjdCwgcHJvcGVydHkpIHsgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsIHByb3BlcnR5KTsgfTtcblxuIFx0Ly8gX193ZWJwYWNrX3B1YmxpY19wYXRoX19cbiBcdF9fd2VicGFja19yZXF1aXJlX18ucCA9IFwiXCI7XG5cblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXyhfX3dlYnBhY2tfcmVxdWlyZV9fLnMgPSBcIi4vcHJldmlldy1zcmMvcHJlLnRzXCIpO1xuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmltcG9ydCB7IE1lc3NhZ2VQb3N0ZXIgfSBmcm9tICcuL21lc3NhZ2luZyc7XG5pbXBvcnQgeyBnZXRTZXR0aW5ncyB9IGZyb20gJy4vc2V0dGluZ3MnO1xuaW1wb3J0IHsgZ2V0U3RyaW5ncyB9IGZyb20gJy4vc3RyaW5ncyc7XG5cbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIENzcEFsZXJ0ZXIge1xuXHRwcml2YXRlIGRpZFNob3cgPSBmYWxzZTtcblx0cHJpdmF0ZSBkaWRIYXZlQ3NwV2FybmluZyA9IGZhbHNlO1xuXG5cdHByaXZhdGUgbWVzc2FnaW5nPzogTWVzc2FnZVBvc3RlcjtcblxuXHRjb25zdHJ1Y3RvcigpIHtcblx0XHRkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdzZWN1cml0eXBvbGljeXZpb2xhdGlvbicsICgpID0+IHtcblx0XHRcdHRoaXMub25Dc3BXYXJuaW5nKCk7XG5cdFx0fSk7XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIChldmVudCkgPT4ge1xuXHRcdFx0aWYgKGV2ZW50ICYmIGV2ZW50LmRhdGEgJiYgZXZlbnQuZGF0YS5uYW1lID09PSAndnNjb2RlLWRpZC1ibG9jay1zdmcnKSB7XG5cdFx0XHRcdHRoaXMub25Dc3BXYXJuaW5nKCk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cblxuXHRwdWJsaWMgc2V0UG9zdGVyKHBvc3RlcjogTWVzc2FnZVBvc3Rlcikge1xuXHRcdHRoaXMubWVzc2FnaW5nID0gcG9zdGVyO1xuXHRcdGlmICh0aGlzLmRpZEhhdmVDc3BXYXJuaW5nKSB7XG5cdFx0XHR0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBvbkNzcFdhcm5pbmcoKSB7XG5cdFx0dGhpcy5kaWRIYXZlQ3NwV2FybmluZyA9IHRydWU7XG5cdFx0dGhpcy5zaG93Q3NwV2FybmluZygpO1xuXHR9XG5cblx0cHJpdmF0ZSBzaG93Q3NwV2FybmluZygpIHtcblx0XHRjb25zdCBzdHJpbmdzID0gZ2V0U3RyaW5ncygpO1xuXHRcdGNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuXHRcdGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdHRoaXMuZGlkU2hvdyA9IHRydWU7XG5cblx0XHRjb25zdCBub3RpZmljYXRpb24gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJyk7XG5cdFx0bm90aWZpY2F0aW9uLmlubmVyVGV4dCA9IHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlVGV4dDtcblx0XHRub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgndGl0bGUnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZVRpdGxlKTtcblxuXHRcdG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgnYXJpYS1sYWJlbCcsIHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlTGFiZWwpO1xuXHRcdG5vdGlmaWNhdGlvbi5vbmNsaWNrID0gKCkgPT4ge1xuXHRcdFx0dGhpcy5tZXNzYWdpbmchLnBvc3RNZXNzYWdlKCdzaG93UHJldmlld1NlY3VyaXR5U2VsZWN0b3InLCB7IHNvdXJjZTogc2V0dGluZ3Muc291cmNlIH0pO1xuXHRcdH07XG5cdFx0ZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuXHR9XG59XG4iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmltcG9ydCB7IE1lc3NhZ2VQb3N0ZXIgfSBmcm9tICcuL21lc3NhZ2luZyc7XG5cbmV4cG9ydCBjbGFzcyBTdHlsZUxvYWRpbmdNb25pdG9yIHtcblx0cHJpdmF0ZSB1bmxvYWRlZFN0eWxlczogc3RyaW5nW10gPSBbXTtcblx0cHJpdmF0ZSBmaW5pc2hlZExvYWRpbmc6IGJvb2xlYW4gPSBmYWxzZTtcblxuXHRwcml2YXRlIHBvc3Rlcj86IE1lc3NhZ2VQb3N0ZXI7XG5cblx0Y29uc3RydWN0b3IoKSB7XG5cdFx0Y29uc3Qgb25TdHlsZUxvYWRFcnJvciA9IChldmVudDogYW55KSA9PiB7XG5cdFx0XHRjb25zdCBzb3VyY2UgPSBldmVudC50YXJnZXQuZGF0YXNldC5zb3VyY2U7XG5cdFx0XHR0aGlzLnVubG9hZGVkU3R5bGVzLnB1c2goc291cmNlKTtcblx0XHR9O1xuXG5cdFx0d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7XG5cdFx0XHRmb3IgKGNvbnN0IGxpbmsgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS11c2VyLXN0eWxlJykgYXMgSFRNTENvbGxlY3Rpb25PZjxIVE1MRWxlbWVudD4pIHtcblx0XHRcdFx0aWYgKGxpbmsuZGF0YXNldC5zb3VyY2UpIHtcblx0XHRcdFx0XHRsaW5rLm9uZXJyb3IgPSBvblN0eWxlTG9hZEVycm9yO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fSk7XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsICgpID0+IHtcblx0XHRcdGlmICghdGhpcy51bmxvYWRlZFN0eWxlcy5sZW5ndGgpIHtcblx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0fVxuXHRcdFx0dGhpcy5maW5pc2hlZExvYWRpbmcgPSB0cnVlO1xuXHRcdFx0aWYgKHRoaXMucG9zdGVyKSB7XG5cdFx0XHRcdHRoaXMucG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuXHRcdFx0fVxuXHRcdH0pO1xuXHR9XG5cblx0cHVibGljIHNldFBvc3Rlcihwb3N0ZXI6IE1lc3NhZ2VQb3N0ZXIpOiB2b2lkIHtcblx0XHR0aGlzLnBvc3RlciA9IHBvc3Rlcjtcblx0XHRpZiAodGhpcy5maW5pc2hlZExvYWRpbmcpIHtcblx0XHRcdHBvc3Rlci5wb3N0TWVzc2FnZSgncHJldmlld1N0eWxlTG9hZEVycm9yJywgeyB1bmxvYWRlZFN0eWxlczogdGhpcy51bmxvYWRlZFN0eWxlcyB9KTtcblx0XHR9XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgQ3NwQWxlcnRlciB9IGZyb20gJy4vY3NwJztcbmltcG9ydCB7IFN0eWxlTG9hZGluZ01vbml0b3IgfSBmcm9tICcuL2xvYWRpbmcnO1xuXG5kZWNsYXJlIGdsb2JhbCB7XG5cdGludGVyZmFjZSBXaW5kb3cge1xuXHRcdGNzcEFsZXJ0ZXI6IENzcEFsZXJ0ZXI7XG5cdFx0c3R5bGVMb2FkaW5nTW9uaXRvcjogU3R5bGVMb2FkaW5nTW9uaXRvcjtcblx0fVxufVxuXG53aW5kb3cuY3NwQWxlcnRlciA9IG5ldyBDc3BBbGVydGVyKCk7XG53aW5kb3cuc3R5bGVMb2FkaW5nTW9uaXRvciA9IG5ldyBTdHlsZUxvYWRpbmdNb25pdG9yKCk7IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmV4cG9ydCBpbnRlcmZhY2UgUHJldmlld1NldHRpbmdzIHtcblx0c291cmNlOiBzdHJpbmc7XG5cdGxpbmU6IG51bWJlcjtcblx0bGluZUNvdW50OiBudW1iZXI7XG5cdHNjcm9sbFByZXZpZXdXaXRoRWRpdG9yPzogYm9vbGVhbjtcblx0c2Nyb2xsRWRpdG9yV2l0aFByZXZpZXc6IGJvb2xlYW47XG5cdGRpc2FibGVTZWN1cml0eVdhcm5pbmdzOiBib29sZWFuO1xuXHRkb3VibGVDbGlja1RvU3dpdGNoVG9FZGl0b3I6IGJvb2xlYW47XG59XG5cbmxldCBjYWNoZWRTZXR0aW5nczogUHJldmlld1NldHRpbmdzIHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGF0YShrZXk6IHN0cmluZyk6IFByZXZpZXdTZXR0aW5ncyB7XG5cdGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuXHRpZiAoZWxlbWVudCkge1xuXHRcdGNvbnN0IGRhdGEgPSBlbGVtZW50LmdldEF0dHJpYnV0ZShrZXkpO1xuXHRcdGlmIChkYXRhKSB7XG5cdFx0XHRyZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcblx0XHR9XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKTogUHJldmlld1NldHRpbmdzIHtcblx0aWYgKGNhY2hlZFNldHRpbmdzKSB7XG5cdFx0cmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuXHR9XG5cblx0Y2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U3RyaW5ncygpOiB7IFtrZXk6IHN0cmluZ106IHN0cmluZyB9IHtcblx0Y29uc3Qgc3RvcmUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuXHRpZiAoc3RvcmUpIHtcblx0XHRjb25zdCBkYXRhID0gc3RvcmUuZ2V0QXR0cmlidXRlKCdkYXRhLXN0cmluZ3MnKTtcblx0XHRpZiAoZGF0YSkge1xuXHRcdFx0cmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG5cdFx0fVxuXHR9XG5cdHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc3RyaW5ncycpO1xufVxuIl0sInNvdXJjZVJvb3QiOiIifQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvY3NwLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2xvYWRpbmcudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvcHJlLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3NldHRpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3N0cmluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0EseURBQWlELGNBQWM7QUFDL0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsbUNBQTJCLDBCQUEwQixFQUFFO0FBQ3ZELHlDQUFpQyxlQUFlO0FBQ2hEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDhEQUFzRCwrREFBK0Q7O0FBRXJIO0FBQ0E7OztBQUdBO0FBQ0E7Ozs7Ozs7Ozs7Ozs7O0FDbkVBOzs7Z0dBR2dHOztBQUdoRyxzRkFBeUM7QUFDekMsbUZBQXVDO0FBRXZDOztHQUVHO0FBQ0gsTUFBYSxVQUFVO0lBTXRCO1FBTFEsWUFBTyxHQUFHLEtBQUssQ0FBQztRQUNoQixzQkFBaUIsR0FBRyxLQUFLLENBQUM7UUFLakMsUUFBUSxDQUFDLGdCQUFnQixDQUFDLHlCQUF5QixFQUFFLEdBQUcsRUFBRTtZQUN6RCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDckIsQ0FBQyxDQUFDLENBQUM7UUFFSCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDNUMsSUFBSSxLQUFLLElBQUksS0FBSyxDQUFDLElBQUksSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxzQkFBc0IsRUFBRTtnQkFDdEUsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO2FBQ3BCO1FBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU0sU0FBUyxDQUFDLE1BQXFCO1FBQ3JDLElBQUksQ0FBQyxTQUFTLEdBQUcsTUFBTSxDQUFDO1FBQ3hCLElBQUksSUFBSSxDQUFDLGlCQUFpQixFQUFFO1lBQzNCLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztTQUN0QjtJQUNGLENBQUM7SUFFTyxZQUFZO1FBQ25CLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7UUFDOUIsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBQ3ZCLENBQUM7SUFFTyxjQUFjO1FBQ3JCLE1BQU0sT0FBTyxHQUFHLG9CQUFVLEVBQUUsQ0FBQztRQUM3QixNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7UUFFL0IsSUFBSSxJQUFJLENBQUMsT0FBTyxJQUFJLFFBQVEsQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUU7WUFDeEUsT0FBTztTQUNQO1FBQ0QsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFFcEIsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNqRCxZQUFZLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQztRQUNyRCxZQUFZLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1FBQ3BELFlBQVksQ0FBQyxZQUFZLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBRWpFLFlBQVksQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzVDLFlBQVksQ0FBQyxZQUFZLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQ3RFLFlBQVksQ0FBQyxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQzNCLElBQUksQ0FBQyxTQUFVLENBQUMsV0FBVyxDQUFDLDZCQUE2QixFQUFFLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQ3pGLENBQUMsQ0FBQztRQUNGLFFBQVEsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ3pDLENBQUM7Q0FDRDtBQW5ERCxnQ0FtREM7Ozs7Ozs7Ozs7Ozs7OztBQ3pERCxNQUFhLG1CQUFtQjtJQU0vQjtRQUxRLG1CQUFjLEdBQWEsRUFBRSxDQUFDO1FBQzlCLG9CQUFlLEdBQVksS0FBSyxDQUFDO1FBS3hDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxLQUFVLEVBQUUsRUFBRTtZQUN2QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7WUFDM0MsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEMsQ0FBQyxDQUFDO1FBRUYsTUFBTSxDQUFDLGdCQUFnQixDQUFDLGtCQUFrQixFQUFFLEdBQUcsRUFBRTtZQUNoRCxLQUFLLE1BQU0sSUFBSSxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxpQkFBaUIsQ0FBa0MsRUFBRTtnQkFDdkcsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRTtvQkFDeEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxnQkFBZ0IsQ0FBQztpQkFDaEM7YUFDRDtRQUNGLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUU7WUFDcEMsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFO2dCQUNoQyxPQUFPO2FBQ1A7WUFDRCxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztZQUM1QixJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUU7Z0JBQ2hCLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO2FBQzFGO1FBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU0sU0FBUyxDQUFDLE1BQXFCO1FBQ3JDLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRTtZQUN6QixNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO1NBQ3JGO0lBQ0YsQ0FBQztDQUNEO0FBckNELGtEQXFDQzs7Ozs7Ozs7Ozs7Ozs7QUMzQ0Q7OztnR0FHZ0c7O0FBRWhHLHVFQUFtQztBQUNuQyxtRkFBZ0Q7QUFTaEQsTUFBTSxDQUFDLFVBQVUsR0FBRyxJQUFJLGdCQUFVLEVBQUUsQ0FBQztBQUNyQyxNQUFNLENBQUMsbUJBQW1CLEdBQUcsSUFBSSw2QkFBbUIsRUFBRSxDQUFDOzs7Ozs7Ozs7Ozs7OztBQ2hCdkQ7OztnR0FHZ0c7O0FBYWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFTLEdBQVc7SUFDMUMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0M7Ozs7Ozs7Ozs7Ozs7O0FDekNEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixVQUFVO0lBQ3pCLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxjQUFjLENBQUMsOEJBQThCLENBQUMsQ0FBQztJQUN0RSxJQUFJLEtBQUssRUFBRTtRQUNWLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDaEQsSUFBSSxJQUFJLEVBQUU7WUFDVCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDeEI7S0FDRDtJQUNELE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztBQUMzQyxDQUFDO0FBVEQsZ0NBU0MiLCJmaWxlIjoicHJlLmpzIiwic291cmNlc0NvbnRlbnQiOlsiIFx0Ly8gVGhlIG1vZHVsZSBjYWNoZVxuIFx0dmFyIGluc3RhbGxlZE1vZHVsZXMgPSB7fTtcblxuIFx0Ly8gVGhlIHJlcXVpcmUgZnVuY3Rpb25cbiBcdGZ1bmN0aW9uIF9fd2VicGFja19yZXF1aXJlX18obW9kdWxlSWQpIHtcblxuIFx0XHQvLyBDaGVjayBpZiBtb2R1bGUgaXMgaW4gY2FjaGVcbiBcdFx0aWYoaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0pIHtcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcbiBcdFx0fVxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0aTogbW9kdWxlSWQsXG4gXHRcdFx0bDogZmFsc2UsXG4gXHRcdFx0ZXhwb3J0czoge31cbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubCA9IHRydWU7XG5cbiBcdFx0Ly8gUmV0dXJuIHRoZSBleHBvcnRzIG9mIHRoZSBtb2R1bGVcbiBcdFx0cmV0dXJuIG1vZHVsZS5leHBvcnRzO1xuIFx0fVxuXG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlcyBvYmplY3QgKF9fd2VicGFja19tb2R1bGVzX18pXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm0gPSBtb2R1bGVzO1xuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZSBjYWNoZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5jID0gaW5zdGFsbGVkTW9kdWxlcztcblxuIFx0Ly8gZGVmaW5lIGdldHRlciBmdW5jdGlvbiBmb3IgaGFybW9ueSBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQgPSBmdW5jdGlvbihleHBvcnRzLCBuYW1lLCBnZXR0ZXIpIHtcbiBcdFx0aWYoIV9fd2VicGFja19yZXF1aXJlX18ubyhleHBvcnRzLCBuYW1lKSkge1xuIFx0XHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBuYW1lLCB7XG4gXHRcdFx0XHRjb25maWd1cmFibGU6IGZhbHNlLFxuIFx0XHRcdFx0ZW51bWVyYWJsZTogdHJ1ZSxcbiBcdFx0XHRcdGdldDogZ2V0dGVyXG4gXHRcdFx0fSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGdldERlZmF1bHRFeHBvcnQgZnVuY3Rpb24gZm9yIGNvbXBhdGliaWxpdHkgd2l0aCBub24taGFybW9ueSBtb2R1bGVzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm4gPSBmdW5jdGlvbihtb2R1bGUpIHtcbiBcdFx0dmFyIGdldHRlciA9IG1vZHVsZSAmJiBtb2R1bGUuX19lc01vZHVsZSA/XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0RGVmYXVsdCgpIHsgcmV0dXJuIG1vZHVsZVsnZGVmYXVsdCddOyB9IDpcbiBcdFx0XHRmdW5jdGlvbiBnZXRNb2R1bGVFeHBvcnRzKCkgeyByZXR1cm4gbW9kdWxlOyB9O1xuIFx0XHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQoZ2V0dGVyLCAnYScsIGdldHRlcik7XG4gXHRcdHJldHVybiBnZXR0ZXI7XG4gXHR9O1xuXG4gXHQvLyBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGxcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubyA9IGZ1bmN0aW9uKG9iamVjdCwgcHJvcGVydHkpIHsgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsIHByb3BlcnR5KTsgfTtcblxuIFx0Ly8gX193ZWJwYWNrX3B1YmxpY19wYXRoX19cbiBcdF9fd2VicGFja19yZXF1aXJlX18ucCA9IFwiXCI7XG5cblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXyhfX3dlYnBhY2tfcmVxdWlyZV9fLnMgPSBcIi4vcHJldmlldy1zcmMvcHJlLnRzXCIpO1xuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmltcG9ydCB7IE1lc3NhZ2VQb3N0ZXIgfSBmcm9tICcuL21lc3NhZ2luZyc7XG5pbXBvcnQgeyBnZXRTZXR0aW5ncyB9IGZyb20gJy4vc2V0dGluZ3MnO1xuaW1wb3J0IHsgZ2V0U3RyaW5ncyB9IGZyb20gJy4vc3RyaW5ncyc7XG5cbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIENzcEFsZXJ0ZXIge1xuXHRwcml2YXRlIGRpZFNob3cgPSBmYWxzZTtcblx0cHJpdmF0ZSBkaWRIYXZlQ3NwV2FybmluZyA9IGZhbHNlO1xuXG5cdHByaXZhdGUgbWVzc2FnaW5nPzogTWVzc2FnZVBvc3RlcjtcblxuXHRjb25zdHJ1Y3RvcigpIHtcblx0XHRkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdzZWN1cml0eXBvbGljeXZpb2xhdGlvbicsICgpID0+IHtcblx0XHRcdHRoaXMub25Dc3BXYXJuaW5nKCk7XG5cdFx0fSk7XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIChldmVudCkgPT4ge1xuXHRcdFx0aWYgKGV2ZW50ICYmIGV2ZW50LmRhdGEgJiYgZXZlbnQuZGF0YS5uYW1lID09PSAndnNjb2RlLWRpZC1ibG9jay1zdmcnKSB7XG5cdFx0XHRcdHRoaXMub25Dc3BXYXJuaW5nKCk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cblxuXHRwdWJsaWMgc2V0UG9zdGVyKHBvc3RlcjogTWVzc2FnZVBvc3Rlcikge1xuXHRcdHRoaXMubWVzc2FnaW5nID0gcG9zdGVyO1xuXHRcdGlmICh0aGlzLmRpZEhhdmVDc3BXYXJuaW5nKSB7XG5cdFx0XHR0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBvbkNzcFdhcm5pbmcoKSB7XG5cdFx0dGhpcy5kaWRIYXZlQ3NwV2FybmluZyA9IHRydWU7XG5cdFx0dGhpcy5zaG93Q3NwV2FybmluZygpO1xuXHR9XG5cblx0cHJpdmF0ZSBzaG93Q3NwV2FybmluZygpIHtcblx0XHRjb25zdCBzdHJpbmdzID0gZ2V0U3RyaW5ncygpO1xuXHRcdGNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuXHRcdGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdHRoaXMuZGlkU2hvdyA9IHRydWU7XG5cblx0XHRjb25zdCBub3RpZmljYXRpb24gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJyk7XG5cdFx0bm90aWZpY2F0aW9uLmlubmVyVGV4dCA9IHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlVGV4dDtcblx0XHRub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgndGl0bGUnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZVRpdGxlKTtcblxuXHRcdG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgnYXJpYS1sYWJlbCcsIHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlTGFiZWwpO1xuXHRcdG5vdGlmaWNhdGlvbi5vbmNsaWNrID0gKCkgPT4ge1xuXHRcdFx0dGhpcy5tZXNzYWdpbmchLnBvc3RNZXNzYWdlKCdzaG93UHJldmlld1NlY3VyaXR5U2VsZWN0b3InLCB7IHNvdXJjZTogc2V0dGluZ3Muc291cmNlIH0pO1xuXHRcdH07XG5cdFx0ZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuXHR9XG59XG4iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmltcG9ydCB7IE1lc3NhZ2VQb3N0ZXIgfSBmcm9tICcuL21lc3NhZ2luZyc7XG5cbmV4cG9ydCBjbGFzcyBTdHlsZUxvYWRpbmdNb25pdG9yIHtcblx0cHJpdmF0ZSB1bmxvYWRlZFN0eWxlczogc3RyaW5nW10gPSBbXTtcblx0cHJpdmF0ZSBmaW5pc2hlZExvYWRpbmc6IGJvb2xlYW4gPSBmYWxzZTtcblxuXHRwcml2YXRlIHBvc3Rlcj86IE1lc3NhZ2VQb3N0ZXI7XG5cblx0Y29uc3RydWN0b3IoKSB7XG5cdFx0Y29uc3Qgb25TdHlsZUxvYWRFcnJvciA9IChldmVudDogYW55KSA9PiB7XG5cdFx0XHRjb25zdCBzb3VyY2UgPSBldmVudC50YXJnZXQuZGF0YXNldC5zb3VyY2U7XG5cdFx0XHR0aGlzLnVubG9hZGVkU3R5bGVzLnB1c2goc291cmNlKTtcblx0XHR9O1xuXG5cdFx0d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7XG5cdFx0XHRmb3IgKGNvbnN0IGxpbmsgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS11c2VyLXN0eWxlJykgYXMgSFRNTENvbGxlY3Rpb25PZjxIVE1MRWxlbWVudD4pIHtcblx0XHRcdFx0aWYgKGxpbmsuZGF0YXNldC5zb3VyY2UpIHtcblx0XHRcdFx0XHRsaW5rLm9uZXJyb3IgPSBvblN0eWxlTG9hZEVycm9yO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fSk7XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsICgpID0+IHtcblx0XHRcdGlmICghdGhpcy51bmxvYWRlZFN0eWxlcy5sZW5ndGgpIHtcblx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0fVxuXHRcdFx0dGhpcy5maW5pc2hlZExvYWRpbmcgPSB0cnVlO1xuXHRcdFx0aWYgKHRoaXMucG9zdGVyKSB7XG5cdFx0XHRcdHRoaXMucG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuXHRcdFx0fVxuXHRcdH0pO1xuXHR9XG5cblx0cHVibGljIHNldFBvc3Rlcihwb3N0ZXI6IE1lc3NhZ2VQb3N0ZXIpOiB2b2lkIHtcblx0XHR0aGlzLnBvc3RlciA9IHBvc3Rlcjtcblx0XHRpZiAodGhpcy5maW5pc2hlZExvYWRpbmcpIHtcblx0XHRcdHBvc3Rlci5wb3N0TWVzc2FnZSgncHJldmlld1N0eWxlTG9hZEVycm9yJywgeyB1bmxvYWRlZFN0eWxlczogdGhpcy51bmxvYWRlZFN0eWxlcyB9KTtcblx0XHR9XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgQ3NwQWxlcnRlciB9IGZyb20gJy4vY3NwJztcbmltcG9ydCB7IFN0eWxlTG9hZGluZ01vbml0b3IgfSBmcm9tICcuL2xvYWRpbmcnO1xuXG5kZWNsYXJlIGdsb2JhbCB7XG5cdGludGVyZmFjZSBXaW5kb3cge1xuXHRcdGNzcEFsZXJ0ZXI6IENzcEFsZXJ0ZXI7XG5cdFx0c3R5bGVMb2FkaW5nTW9uaXRvcjogU3R5bGVMb2FkaW5nTW9uaXRvcjtcblx0fVxufVxuXG53aW5kb3cuY3NwQWxlcnRlciA9IG5ldyBDc3BBbGVydGVyKCk7XG53aW5kb3cuc3R5bGVMb2FkaW5nTW9uaXRvciA9IG5ldyBTdHlsZUxvYWRpbmdNb25pdG9yKCk7IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmV4cG9ydCBpbnRlcmZhY2UgUHJldmlld1NldHRpbmdzIHtcblx0cmVhZG9ubHkgc291cmNlOiBzdHJpbmc7XG5cdHJlYWRvbmx5IGxpbmU6IG51bWJlcjtcblx0cmVhZG9ubHkgbGluZUNvdW50OiBudW1iZXI7XG5cdHJlYWRvbmx5IHNjcm9sbFByZXZpZXdXaXRoRWRpdG9yPzogYm9vbGVhbjtcblx0cmVhZG9ubHkgc2Nyb2xsRWRpdG9yV2l0aFByZXZpZXc6IGJvb2xlYW47XG5cdHJlYWRvbmx5IGRpc2FibGVTZWN1cml0eVdhcm5pbmdzOiBib29sZWFuO1xuXHRyZWFkb25seSBkb3VibGVDbGlja1RvU3dpdGNoVG9FZGl0b3I6IGJvb2xlYW47XG5cdHJlYWRvbmx5IHdlYnZpZXdSZXNvdXJjZVJvb3Q6IHN0cmluZztcbn1cblxubGV0IGNhY2hlZFNldHRpbmdzOiBQcmV2aWV3U2V0dGluZ3MgfCB1bmRlZmluZWQgPSB1bmRlZmluZWQ7XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXREYXRhPFQgPSB7fT4oa2V5OiBzdHJpbmcpOiBUIHtcblx0Y29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG5cdGlmIChlbGVtZW50KSB7XG5cdFx0Y29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG5cdFx0aWYgKGRhdGEpIHtcblx0XHRcdHJldHVybiBKU09OLnBhcnNlKGRhdGEpO1xuXHRcdH1cblx0fVxuXG5cdHRocm93IG5ldyBFcnJvcihgQ291bGQgbm90IGxvYWQgZGF0YSBmb3IgJHtrZXl9YCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRTZXR0aW5ncygpOiBQcmV2aWV3U2V0dGluZ3Mge1xuXHRpZiAoY2FjaGVkU2V0dGluZ3MpIHtcblx0XHRyZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG5cdH1cblxuXHRjYWNoZWRTZXR0aW5ncyA9IGdldERhdGEoJ2RhdGEtc2V0dGluZ3MnKTtcblx0aWYgKGNhY2hlZFNldHRpbmdzKSB7XG5cdFx0cmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuXHR9XG5cblx0dGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzZXR0aW5ncycpO1xufVxuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRTdHJpbmdzKCk6IHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH0ge1xuXHRjb25zdCBzdG9yZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG5cdGlmIChzdG9yZSkge1xuXHRcdGNvbnN0IGRhdGEgPSBzdG9yZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtc3RyaW5ncycpO1xuXHRcdGlmIChkYXRhKSB7XG5cdFx0XHRyZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcblx0XHR9XG5cdH1cblx0dGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzdHJpbmdzJyk7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 0354cb39604..8e841a982aa 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -4,7 +4,8 @@ "description": "%description%", "version": "1.0.0", "icon": "icon.png", - "publisher": "vscode", + "publisher": "vscode", + "enableProposedApi": true, "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "engines": { diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 567b915bc53..f78ae2f7972 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -19,7 +19,7 @@ const settings = getSettings(); const vscode = acquireVsCodeApi(); // Set VS Code state -let state = getData('data-state'); +let state = getData<{ line: number }>('data-state'); vscode.setState(state); const messaging = createPosterForVsCode(vscode); @@ -131,8 +131,8 @@ document.addEventListener('click', event => { if (node.getAttribute('href').startsWith('#')) { break; } - if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:')) { - const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').split('#'); + if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:') || node.href.startsWith(settings.webviewResourceRoot)) { + const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').replace(new RegExp(`^${escapeRegExp(settings.webviewResourceRoot)}`)).split('#'); messaging.postMessage('clickLink', { path, fragment }); event.preventDefault(); event.stopPropagation(); @@ -157,4 +157,8 @@ if (settings.scrollEditorWithPreview) { } } }, 50)); +} + +function escapeRegExp(text: string) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); } \ No newline at end of file diff --git a/extensions/markdown-language-features/preview-src/settings.ts b/extensions/markdown-language-features/preview-src/settings.ts index 46b03544c09..c77081b2659 100644 --- a/extensions/markdown-language-features/preview-src/settings.ts +++ b/extensions/markdown-language-features/preview-src/settings.ts @@ -4,18 +4,19 @@ *--------------------------------------------------------------------------------------------*/ export interface PreviewSettings { - source: string; - line: number; - lineCount: number; - scrollPreviewWithEditor?: boolean; - scrollEditorWithPreview: boolean; - disableSecurityWarnings: boolean; - doubleClickToSwitchToEditor: boolean; + readonly source: string; + readonly line: number; + readonly lineCount: number; + readonly scrollPreviewWithEditor?: boolean; + readonly scrollEditorWithPreview: boolean; + readonly disableSecurityWarnings: boolean; + readonly doubleClickToSwitchToEditor: boolean; + readonly webviewResourceRoot: string; } let cachedSettings: PreviewSettings | undefined = undefined; -export function getData(key: string): PreviewSettings { +export function getData(key: string): T { const element = document.getElementById('vscode-markdown-preview-data'); if (element) { const data = element.getAttribute(key); diff --git a/extensions/markdown-language-features/src/features/documentLinkProvider.ts b/extensions/markdown-language-features/src/features/documentLinkProvider.ts index 9ae4bcfed29..c4344a632bd 100644 --- a/extensions/markdown-language-features/src/features/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/features/documentLinkProvider.ts @@ -39,7 +39,7 @@ function parseLink( return { uri: OpenDocumentLinkCommand.createCommandUri(resourcePath, tempUri.fragment), - tooltip: localize('documentLink.tooltip', 'follow link') + tooltip: localize('documentLink.tooltip', 'Follow link') }; } diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 191b037795a..1479a6ea112 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -412,7 +412,7 @@ export class MarkdownPreview extends Disposable { this.currentVersion = pendingVersion; if (this._resource === resource) { - const content = await this._contentProvider.provideTextDocumentContent(document, this._previewConfigurations, this.line, this.state); + const content = await this._contentProvider.provideTextDocumentContent(document, await this.editor.webview.resourceRoot, this._previewConfigurations, this.line, this.state); // Another call to `doUpdate` may have happened. // Make sure we are still updating for the correct document if (this.currentVersion && this.currentVersion.equals(pendingVersion)) { diff --git a/extensions/markdown-language-features/src/features/previewContentProvider.ts b/extensions/markdown-language-features/src/features/previewContentProvider.ts index 486ce5d9db7..00bcf723aab 100644 --- a/extensions/markdown-language-features/src/features/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/features/previewContentProvider.ts @@ -14,6 +14,7 @@ import { Logger } from '../logger'; import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from '../security'; import { MarkdownPreviewConfigurationManager, MarkdownPreviewConfiguration } from './previewConfig'; import { MarkdownContributionProvider } from '../markdownExtensions'; +import { toResoruceUri } from '../util/resources'; /** * Strings used inside the markdown preview. @@ -50,6 +51,7 @@ export class MarkdownContentProvider { public async provideTextDocumentContent( markdownDocument: vscode.TextDocument, + webviewResourceRoot: string, previewConfigurations: MarkdownPreviewConfigurationManager, initialLine: number | undefined = undefined, state?: any @@ -63,14 +65,15 @@ export class MarkdownContentProvider { scrollPreviewWithEditor: config.scrollPreviewWithEditor, scrollEditorWithPreview: config.scrollEditorWithPreview, doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor, - disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings() + disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings(), + webviewResourceRoot: webviewResourceRoot, }; this.logger.log('provideTextDocumentContent', initialData); // Content Security Policy const nonce = new Date().getTime() + '' + new Date().getMilliseconds(); - const csp = this.getCspForResource(sourceUri, nonce); + const csp = this.getCspForResource(webviewResourceRoot, sourceUri, nonce); const body = await this.engine.render(markdownDocument); return ` @@ -82,14 +85,14 @@ export class MarkdownContentProvider { data-settings="${escapeAttribute(JSON.stringify(initialData))}" data-strings="${escapeAttribute(JSON.stringify(previewStrings))}" data-state="${escapeAttribute(JSON.stringify(state || {}))}"> - - ${this.getStyles(sourceUri, nonce, config, state)} - + + ${this.getStyles(webviewResourceRoot, sourceUri, nonce, config, state)} + ${body}
- ${this.getScripts(nonce)} + ${this.getScripts(webviewResourceRoot, nonce)} `; } @@ -107,13 +110,12 @@ export class MarkdownContentProvider { `; } - private extensionResourcePath(mediaFile: string): string { - return vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile))) - .with({ scheme: 'vscode-resource' }) + private extensionResourcePath(webviewResourceRoot: string, mediaFile: string): string { + return toResoruceUri(webviewResourceRoot, vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile)))) .toString(); } - private fixHref(resource: vscode.Uri, href: string): string { + private fixHref(webviewResourceRoot: string, resource: vscode.Uri, href: string): string { if (!href) { return href; } @@ -124,29 +126,23 @@ export class MarkdownContentProvider { // Assume it must be a local file if (path.isAbsolute(href)) { - return vscode.Uri.file(href) - .with({ scheme: 'vscode-resource' }) - .toString(); + return toResoruceUri(webviewResourceRoot, vscode.Uri.file(href)).toString(); } // Use a workspace relative path if there is a workspace const root = vscode.workspace.getWorkspaceFolder(resource); if (root) { - return vscode.Uri.file(path.join(root.uri.fsPath, href)) - .with({ scheme: 'vscode-resource' }) - .toString(); + return toResoruceUri(webviewResourceRoot, vscode.Uri.file(path.join(root.uri.fsPath, href))).toString(); } // Otherwise look relative to the markdown file - return vscode.Uri.file(path.join(path.dirname(resource.fsPath), href)) - .with({ scheme: 'vscode-resource' }) - .toString(); + return toResoruceUri(webviewResourceRoot, vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))).toString(); } - private computeCustomStyleSheetIncludes(resource: vscode.Uri, config: MarkdownPreviewConfiguration): string { + private computeCustomStyleSheetIncludes(webviewResourceRoot: string, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string { if (Array.isArray(config.styles)) { return config.styles.map(style => { - return ``; + return ``; }).join('\n'); } return ''; @@ -177,37 +173,41 @@ export class MarkdownContentProvider { return ret; } - private getStyles(resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration, state?: any): string { + private getStyles(webviewResourceRoot: string, resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration, state?: any): string { const baseStyles = this.contributionProvider.contributions.previewStyles - .map(resource => ``) + .map(resource => ``) .join('\n'); return `${baseStyles} ${this.getSettingsOverrideStyles(nonce, config)} - ${this.computeCustomStyleSheetIncludes(resource, config)} + ${this.computeCustomStyleSheetIncludes(webviewResourceRoot, resource, config)} ${this.getImageStabilizerStyles(state)}`; } - private getScripts(nonce: string): string { + private getScripts(resourceRoot: string, nonce: string): string { return this.contributionProvider.contributions.previewScripts - .map(resource => ``) + .map(resource => ``) .join('\n'); } - private getCspForResource(resource: vscode.Uri, nonce: string): string { + private getCspForResource( + webviewResourceRoot: string, + resource: vscode.Uri, + nonce: string + ): string { switch (this.cspArbiter.getSecurityLevelForResource(resource)) { case MarkdownPreviewSecurityLevel.AllowInsecureContent: - return ``; + return ``; case MarkdownPreviewSecurityLevel.AllowInsecureLocalContent: - return ``; + return ``; case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent: return ''; case MarkdownPreviewSecurityLevel.Strict: default: - return ``; + return ``; } } } diff --git a/extensions/markdown-language-features/src/markdownExtensions.ts b/extensions/markdown-language-features/src/markdownExtensions.ts index 65899874f5a..b03c5df80bd 100644 --- a/extensions/markdown-language-features/src/markdownExtensions.ts +++ b/extensions/markdown-language-features/src/markdownExtensions.ts @@ -9,8 +9,7 @@ import { Disposable } from './util/dispose'; import * as arrays from './util/arrays'; const resolveExtensionResource = (extension: vscode.Extension, resourcePath: string): vscode.Uri => { - return vscode.Uri.file(path.join(extension.extensionPath, resourcePath)) - .with({ scheme: 'vscode-resource' }); + return vscode.Uri.file(path.join(extension.extensionPath, resourcePath)); }; const resolveExtensionResources = (extension: vscode.Extension, resourcePaths: unknown): vscode.Uri[] => { diff --git a/build/npm/install-server.js b/extensions/markdown-language-features/src/util/resources.ts similarity index 54% rename from build/npm/install-server.js rename to extensions/markdown-language-features/src/util/resources.ts index ffeec1fa30a..cb9b8cb56d6 100644 --- a/build/npm/install-server.js +++ b/extensions/markdown-language-features/src/util/resources.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const cp = require('child_process'); +import * as vscode from 'vscode'; -function exec(cmdLine) { - console.log(cmdLine); - cp.execSync(cmdLine, {stdio: "inherit"}); + +export function toResoruceUri(webviewResourceRoot: string, uri: vscode.Uri): vscode.Uri { + const rootUri = vscode.Uri.parse(webviewResourceRoot); + return rootUri.with({ + path: rootUri.path + uri.path, + query: uri.query, + fragment: uri.fragment, + }); } - -exec('git fetch distro'); -exec(`git checkout ${process.env['npm_package_distro']} -- src/vs/server resources/server`); -exec('git reset HEAD src/vs/server resources/server'); \ No newline at end of file diff --git a/extensions/shellscript/language-configuration.json b/extensions/shellscript/language-configuration.json index 01b6a8a2823..964623ac4cc 100644 --- a/extensions/shellscript/language-configuration.json +++ b/extensions/shellscript/language-configuration.json @@ -22,5 +22,11 @@ ["\"", "\""], ["'", "'"], ["`", "`"] - ] -} \ No newline at end of file + ], + "folding": { + "markers": { + "start": "^\\s*#\\s*#?region\\b.*", + "end": "^\\s*#\\s*#?endregion\\b.*" + } + } +} diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index 18403663110..a55af2b08ff 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -14,7 +14,7 @@ "id": "shellscript", "aliases": ["Shell Script", "shellscript", "bash", "sh", "zsh", "ksh"], "extensions": [".sh", ".bash", ".bashrc", ".bash_aliases", ".bash_profile", ".bash_login", ".ebuild", ".install", ".profile", ".bash_logout", ".zsh", ".zshrc", ".zprofile", ".zlogin", ".zlogout", ".zshenv", ".zsh-theme", ".ksh"], - "filenames": ["PKGBUILD"], + "filenames": ["APKBUILD", "PKGBUILD"], "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh|ksh|ash|qsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", "configuration": "./language-configuration.json", "mimetypes": ["text/x-shellscript"] diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 86f996d0649..00c0445e730 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -591,6 +591,12 @@ "default": true, "description": "%configuration.surveys.enabled%", "scope": "window" + }, + "typescript.experimental.useSeparateSyntaxServer": { + "type": "boolean", + "default": false, + "description": "%configuration.experimental.useSeparateSyntaxServer%", + "scope": "window" } } }, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 7e31edca479..10fdddec382 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -49,6 +49,7 @@ "typescript.problemMatchers.tsc.label": "TypeScript problems", "typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)", "configuration.suggest.paths": "Enable/disable suggestions for paths in import statements and require calls.", + "configuration.experimental.useSeparateSyntaxServer": "Enable/disable spawning a separate TypeScript server that can more quickly respond to syntax related operations, such as calculating folding or computing document symbols. Requires using TypeScript 3.4.0 or newer in the workspace.", "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Requires using TypeScript 2.6.0 or newer in the workspace. Default of `null` uses VS Code's locale.", "javascript.implicitProjectConfig.experimentalDecorators": "Enable/disable `experimentalDecorators` for JavaScript files that are not part of a project. Existing jsconfig.json or tsconfig.json files override this setting. Requires using TypeScript 2.3.1 or newer in the workspace.", "configuration.suggest.autoImports": "Enable/disable auto import suggestions. Requires using TypeScript 2.6.1 or newer in the workspace.", diff --git a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts index e730f8e8be6..25ffb4c81bd 100644 --- a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts @@ -470,20 +470,20 @@ export default class BufferSyncSupport extends Disposable { private sendPendingDiagnostics(): void { const orderedFileSet = this.pendingDiagnostics.getOrderedFileSet(); + if (this.pendingGetErr) { + this.pendingGetErr.cancel(); + + for (const file of this.pendingGetErr.files.entries) { + orderedFileSet.set(file.resource, undefined); + } + } + // Add all open TS buffers to the geterr request. They might be visible for (const buffer of this.syncedBuffers.values) { orderedFileSet.set(buffer.resource, undefined); } if (orderedFileSet.size) { - if (this.pendingGetErr) { - this.pendingGetErr.cancel(); - - for (const file of this.pendingGetErr.files.entries) { - orderedFileSet.set(file.resource, undefined); - } - } - const getErr = this.pendingGetErr = GetErrRequest.executeGetErrRequest(this.client, orderedFileSet, () => { if (this.pendingGetErr === getErr) { this.pendingGetErr = undefined; diff --git a/extensions/typescript-language-features/src/features/definitions.ts b/extensions/typescript-language-features/src/features/definitions.ts index 5fe72c2005f..fd3a1f10e79 100644 --- a/extensions/typescript-language-features/src/features/definitions.ts +++ b/extensions/typescript-language-features/src/features/definitions.ts @@ -37,10 +37,18 @@ export default class TypeScriptDefinitionProvider extends DefinitionProviderBase return response.body.definitions .map((location): vscode.DefinitionLink => { const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location); + if ((location as any).contextStart) { + return { + originSelectionRange: span, + targetRange: typeConverters.Range.fromLocations((location as any).contextStart, (location as any).contextEnd), + targetUri: target.uri, + targetSelectionRange: target.range, + }; + } return { originSelectionRange: span, targetRange: target.range, - targetUri: target.uri, + targetUri: target.uri }; }); } diff --git a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts index 63cfc6222d9..c732ade5223 100644 --- a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts @@ -26,7 +26,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip const codeLens = inputCodeLens as ReferencesCodeLens; const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); - const response = await this.client.execute('implementation', args, token, /* lowPriority */ true); + const response = await this.client.execute('implementation', args, token, { lowPriority: true }); if (response.type !== 'response' || !response.body) { codeLens.command = response.type === 'cancelled' ? TypeScriptBaseCodeLensProvider.cancelledCommand diff --git a/extensions/typescript-language-features/src/features/referencesCodeLens.ts b/extensions/typescript-language-features/src/features/referencesCodeLens.ts index ff09a9a4346..65c2cd5eb7f 100644 --- a/extensions/typescript-language-features/src/features/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/features/referencesCodeLens.ts @@ -22,7 +22,7 @@ class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvide public async resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise { const codeLens = inputCodeLens as ReferencesCodeLens; const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); - const response = await this.client.execute('references', args, token, /* lowPriority */ true); + const response = await this.client.execute('references', args, token, { lowPriority: true }); if (response.type !== 'response' || !response.body) { codeLens.command = response.type === 'cancelled' ? TypeScriptBaseCodeLensProvider.cancelledCommand diff --git a/extensions/typescript-language-features/src/features/task.ts b/extensions/typescript-language-features/src/features/task.ts index 8c393ade672..ae530bcdffe 100644 --- a/extensions/typescript-language-features/src/features/task.ts +++ b/extensions/typescript-language-features/src/features/task.ts @@ -234,8 +234,10 @@ class TscTaskProvider implements vscode.TaskProvider { private getLabelForTasks(project: TSConfig): string { if (project.workspaceFolder) { - return path.posix.relative(project.workspaceFolder.uri.path, project.posixPath); + const workspaceNormalizedUri = vscode.Uri.file(path.normalize(project.workspaceFolder.uri.fsPath)); // Make sure the drive letter is lowercase + return path.posix.relative(workspaceNormalizedUri.path, project.posixPath); } + return project.posixPath; } diff --git a/extensions/typescript-language-features/src/test/server.test.ts b/extensions/typescript-language-features/src/test/server.test.ts index bd22203d8d3..651967fc12c 100644 --- a/extensions/typescript-language-features/src/test/server.test.ts +++ b/extensions/typescript-language-features/src/test/server.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as stream from 'stream'; -import { PipeRequestCanceller, ServerProcess, TypeScriptServer } from '../tsServer/server'; +import { PipeRequestCanceller, TsServerProcess, ProcessBasedTsServer } from '../tsServer/server'; import { nulToken } from '../utils/cancellation'; import Logger from '../utils/logger'; import TelemetryReporter from '../utils/telemetry'; @@ -19,7 +19,7 @@ const NoopTelemetryReporter = new class implements TelemetryReporter { dispose(): void { /* noop */ } }; -class FakeServerProcess implements ServerProcess { +class FakeServerProcess implements TsServerProcess { private readonly _out: stream.PassThrough; private readonly writeListeners = new Set<(data: Buffer) => void>(); @@ -62,7 +62,7 @@ suite('Server', () => { test('should send requests with increasing sequence numbers', async () => { const process = new FakeServerProcess(); - const server = new TypeScriptServer(process, undefined, new PipeRequestCanceller(undefined, tracer), undefined!, NoopTelemetryReporter, tracer); + const server = new ProcessBasedTsServer('semantic', process, undefined, new PipeRequestCanceller('semantic', undefined, tracer), undefined!, NoopTelemetryReporter, tracer); const onWrite1 = process.onWrite(); server.executeImpl('geterr', {}, { isAsync: false, token: nulToken, expectsResult: true }); diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 10a001d52c2..c51aab0fa4b 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -3,248 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as child_process from 'child_process'; import * as fs from 'fs'; -import * as path from 'path'; import * as stream from 'stream'; import * as vscode from 'vscode'; import * as Proto from '../protocol'; -import { ServerResponse } from '../typescriptService'; -import API from '../utils/api'; -import { TsServerLogLevel, TypeScriptServiceConfiguration } from '../utils/configuration'; +import { ServerResponse, TypeScriptRequests } from '../typescriptService'; import { Disposable } from '../utils/dispose'; -import * as electron from '../utils/electron'; -import LogDirectoryProvider from '../utils/logDirectoryProvider'; -import Logger from '../utils/logger'; -import { TypeScriptPluginPathsProvider } from '../utils/pluginPathsProvider'; -import { PluginManager } from '../utils/plugins'; -import { escapeRegExp } from '../utils/regexp'; import TelemetryReporter from '../utils/telemetry'; import Tracer from '../utils/tracer'; -import { TypeScriptVersion, TypeScriptVersionProvider } from '../utils/versionProvider'; +import { TypeScriptVersion } from '../utils/versionProvider'; import { Reader } from '../utils/wireProtocol'; import { CallbackMap } from './callbackMap'; import { RequestItem, RequestQueue, RequestQueueingType } from './requestQueue'; - -class TypeScriptServerError extends Error { - - public static create( - version: TypeScriptVersion, - response: Proto.Response, - ): TypeScriptServerError { - const parsedResult = TypeScriptServerError.parseErrorText(version, response); - return new TypeScriptServerError(version, response, - parsedResult ? parsedResult.message : undefined, - parsedResult ? parsedResult.stack : undefined); - } - - constructor( - version: TypeScriptVersion, - private readonly response: Proto.Response, - public readonly serverMessage: string | undefined, - public readonly serverStack: string | undefined, - ) { - super(`TypeScript Server Error (${version.versionString})\n${serverMessage}\n${serverStack}`); - } - - public get serverErrorText() { - return this.response.message; - } - - public get serverCommand() { - return this.response.command; - } - - /** - * Given a `errorText` from a tsserver request indicating failure in handling a request, - * prepares a payload for telemetry-logging. - */ - private static parseErrorText( - version: TypeScriptVersion, - response: Proto.Response, - ) { - const errorText = response.message; - if (errorText) { - const errorPrefix = 'Error processing request. '; - if (errorText.startsWith(errorPrefix)) { - const prefixFreeErrorText = errorText.substr(errorPrefix.length); - const newlineIndex = prefixFreeErrorText.indexOf('\n'); - if (newlineIndex >= 0) { - // Newline expected between message and stack. - return { - message: prefixFreeErrorText.substring(0, newlineIndex), - stack: TypeScriptServerError.normalizeMessageStack(version, prefixFreeErrorText.substring(newlineIndex + 1)) - }; - } - } - } - return undefined; - } - - /** - * Try to replace full TS Server paths with 'tsserver.js' so that we don't have to post process the data as much - */ - private static normalizeMessageStack( - version: TypeScriptVersion, - message: string | undefined, - ) { - if (!message) { - return ''; - } - return message.replace(new RegExp(`${escapeRegExp(version.path)}[/\\\\]tsserver.js:`, 'gi'), 'tsserver.js:'); - } -} - -export class TypeScriptServerSpawner { - public constructor( - private readonly _versionProvider: TypeScriptVersionProvider, - private readonly _logDirectoryProvider: LogDirectoryProvider, - private readonly _pluginPathsProvider: TypeScriptPluginPathsProvider, - private readonly _logger: Logger, - private readonly _telemetryReporter: TelemetryReporter, - private readonly _tracer: Tracer, - ) { } - - public spawn( - version: TypeScriptVersion, - configuration: TypeScriptServiceConfiguration, - pluginManager: PluginManager - ): TypeScriptServer { - const apiVersion = version.version || API.defaultVersion; - - const { args, cancellationPipeName, tsServerLogFile } = this.getTsServerArgs(configuration, version, apiVersion, pluginManager); - - if (TypeScriptServerSpawner.isLoggingEnabled(apiVersion, configuration)) { - if (tsServerLogFile) { - this._logger.info(`TSServer log file: ${tsServerLogFile}`); - } else { - this._logger.error('Could not create TSServer log directory'); - } - } - - this._logger.info('Forking TSServer'); - const childProcess = electron.fork(version.tsServerPath, args, this.getForkOptions()); - this._logger.info('Started TSServer'); - - return new TypeScriptServer( - new ChildServerProcess(childProcess), - tsServerLogFile, - new PipeRequestCanceller(cancellationPipeName, this._tracer), - version, - this._telemetryReporter, - this._tracer); - } - - private getForkOptions() { - const debugPort = TypeScriptServerSpawner.getDebugPort(); - const tsServerForkOptions: electron.ForkOptions = { - execArgv: debugPort ? [`--inspect=${debugPort}`] : [], - }; - return tsServerForkOptions; - } - - private getTsServerArgs( - configuration: TypeScriptServiceConfiguration, - currentVersion: TypeScriptVersion, - apiVersion: API, - pluginManager: PluginManager, - ): { args: string[], cancellationPipeName: string | undefined, tsServerLogFile: string | undefined } { - const args: string[] = []; - let cancellationPipeName: string | undefined; - let tsServerLogFile: string | undefined; - - if (apiVersion.gte(API.v206)) { - if (apiVersion.gte(API.v250)) { - args.push('--useInferredProjectPerProjectRoot'); - } else { - args.push('--useSingleInferredProject'); - } - - if (configuration.disableAutomaticTypeAcquisition) { - args.push('--disableAutomaticTypingAcquisition'); - } - } - - if (apiVersion.gte(API.v208)) { - args.push('--enableTelemetry'); - } - - if (apiVersion.gte(API.v222)) { - cancellationPipeName = electron.getTempFile('tscancellation'); - args.push('--cancellationPipeName', cancellationPipeName + '*'); - } - - if (TypeScriptServerSpawner.isLoggingEnabled(apiVersion, configuration)) { - const logDir = this._logDirectoryProvider.getNewLogDirectory(); - if (logDir) { - tsServerLogFile = path.join(logDir, `tsserver.log`); - args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel)); - args.push('--logFile', tsServerLogFile); - } - } - - if (apiVersion.gte(API.v230)) { - const pluginPaths = this._pluginPathsProvider.getPluginPaths(); - - if (pluginManager.plugins.length) { - args.push('--globalPlugins', pluginManager.plugins.map(x => x.name).join(',')); - - const isUsingBundledTypeScriptVersion = currentVersion.path === this._versionProvider.defaultVersion.path; - for (const plugin of pluginManager.plugins) { - if (isUsingBundledTypeScriptVersion || plugin.enableForWorkspaceTypeScriptVersions) { - pluginPaths.push(plugin.path); - } - } - } - - if (pluginPaths.length !== 0) { - args.push('--pluginProbeLocations', pluginPaths.join(',')); - } - } - - if (apiVersion.gte(API.v234)) { - if (configuration.npmLocation) { - args.push('--npmLocation', `"${configuration.npmLocation}"`); - } - } - - if (apiVersion.gte(API.v260)) { - args.push('--locale', TypeScriptServerSpawner.getTsLocale(configuration)); - } - - if (apiVersion.gte(API.v291)) { - args.push('--noGetErrOnBackgroundUpdate'); - } - - if (apiVersion.gte(API.v345)) { - args.push('--validateDefaultNpmLocation'); - } - - return { args, cancellationPipeName, tsServerLogFile }; - } - - private static getDebugPort(): number | undefined { - const value = process.env['TSS_DEBUG']; - if (value) { - const port = parseInt(value); - if (!isNaN(port)) { - return port; - } - } - return undefined; - } - - private static isLoggingEnabled(apiVersion: API, configuration: TypeScriptServiceConfiguration) { - return apiVersion.gte(API.v222) && - configuration.tsServerLogLevel !== TsServerLogLevel.Off; - } - - private static getTsLocale(configuration: TypeScriptServiceConfiguration): string { - return configuration.locale - ? configuration.locale - : vscode.env.language; - } -} +import { TypeScriptServerError } from './serverError'; export interface OngoingRequestCanceller { tryCancelOngoingRequest(seq: number): boolean; @@ -252,6 +23,7 @@ export interface OngoingRequestCanceller { export class PipeRequestCanceller implements OngoingRequestCanceller { public constructor( + private readonly _serverId: string, private readonly _cancellationPipeName: string | undefined, private readonly _tracer: Tracer, ) { } @@ -260,7 +32,7 @@ export class PipeRequestCanceller implements OngoingRequestCanceller { if (!this._cancellationPipeName) { return false; } - this._tracer.logTrace(`TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`); + this._tracer.logTrace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`); try { fs.writeFileSync(this._cancellationPipeName + seq, ''); } catch { @@ -270,7 +42,24 @@ export class PipeRequestCanceller implements OngoingRequestCanceller { } } -export interface ServerProcess { +export interface ITypeScriptServer { + readonly onEvent: vscode.Event; + readonly onExit: vscode.Event; + readonly onError: vscode.Event; + readonly onReaderError: vscode.Event; + + readonly tsServerLogFile: string | undefined; + + kill(): void; + + executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; + executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; + executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined; + + dispose(): void; +} + +export interface TsServerProcess { readonly stdout: stream.Readable; write(serverRequest: Proto.Request): void; @@ -280,37 +69,15 @@ export interface ServerProcess { kill(): void; } -class ChildServerProcess implements ServerProcess { - - public constructor( - private readonly _process: child_process.ChildProcess, - ) { } - - get stdout(): stream.Readable { return this._process.stdout!; } - - write(serverRequest: Proto.Request): void { - this._process.stdin!.write(JSON.stringify(serverRequest) + '\r\n', 'utf8'); - } - - on(name: 'exit', handler: (code: number | null) => void): void; - on(name: 'error', handler: (error: Error) => void): void; - on(name: any, handler: any) { - this._process.on(name, handler); - } - - kill(): void { - this._process.kill(); - } -} - -export class TypeScriptServer extends Disposable { +export class ProcessBasedTsServer extends Disposable implements ITypeScriptServer { private readonly _reader: Reader; private readonly _requestQueue = new RequestQueue(); private readonly _callbacks = new CallbackMap(); private readonly _pendingResponses = new Set(); constructor( - private readonly _process: ServerProcess, + private readonly _serverId: string, + private readonly _process: TsServerProcess, private readonly _tsServerLogFile: string | undefined, private readonly _requestCanceller: OngoingRequestCanceller, private readonly _version: TypeScriptVersion, @@ -371,11 +138,11 @@ export class TypeScriptServer extends Disposable { const seq = (event as Proto.RequestCompletedEvent).body.request_seq; const p = this._callbacks.fetch(seq); if (p) { - this._tracer.traceRequestCompleted('requestCompleted', seq, p.startTime); + this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, p.startTime); p.onSuccess(undefined); } } else { - this._tracer.traceEvent(event); + this._tracer.traceEvent(this._serverId, event); this._onEvent.fire(event); } break; @@ -391,7 +158,7 @@ export class TypeScriptServer extends Disposable { private tryCancelRequest(seq: number, command: string): boolean { try { if (this._requestQueue.tryDeletePendingRequest(seq)) { - this._tracer.logTrace(`TypeScript Server: canceled request with sequence number ${seq}`); + this.logTrace(`Canceled request with sequence number ${seq}`); return true; } @@ -399,7 +166,7 @@ export class TypeScriptServer extends Disposable { return true; } - this._tracer.logTrace(`TypeScript Server: tried to cancel request with sequence number ${seq}. But request got already delivered.`); + this.logTrace(`Tried to cancel request with sequence number ${seq}. But request got already delivered.`); return false; } finally { const callback = this.fetchCallback(seq); @@ -415,26 +182,26 @@ export class TypeScriptServer extends Disposable { return; } - this._tracer.traceResponse(response, callback.startTime); + this._tracer.traceResponse(this._serverId, response, callback.startTime); if (response.success) { callback.onSuccess(response); } else if (response.message === 'No content available.') { // Special case where response itself is successful but there is not any data to return. callback.onSuccess(ServerResponse.NoContent); } else { - callback.onError(TypeScriptServerError.create(this._version, response)); + callback.onError(TypeScriptServerError.create(this._serverId, this._version, response)); } } - public executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; - public executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; - public executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { const request = this._requestQueue.createRequest(command, args); const requestInfo: RequestItem = { request, expectsResponse: executeInfo.expectsResult, isAsync: executeInfo.isAsync, - queueingType: getQueueingType(command, executeInfo.lowPriority) + queueingType: ProcessBasedTsServer.getQueueingType(command, executeInfo.lowPriority) }; let result: Promise> | undefined; if (executeInfo.expectsResult) { @@ -490,7 +257,7 @@ export class TypeScriptServer extends Disposable { private sendRequest(requestItem: RequestItem): void { const serverRequest = requestItem.request; - this._tracer.traceRequest(serverRequest, requestItem.expectsResponse, this._requestQueue.length); + this._tracer.traceRequest(this._serverId, serverRequest, requestItem.expectsResponse, this._requestQueue.length); if (requestItem.expectsResponse && !requestItem.isAsync) { this._pendingResponses.add(requestItem.request.seq); @@ -515,17 +282,112 @@ export class TypeScriptServer extends Disposable { this._pendingResponses.delete(seq); return callback; } -} -const fenceCommands = new Set(['change', 'close', 'open', 'updateOpen']); - -function getQueueingType( - command: string, - lowPriority?: boolean -): RequestQueueingType { - if (fenceCommands.has(command)) { - return RequestQueueingType.Fence; + private logTrace(message: string) { + this._tracer.logTrace(this._serverId, message); + } + + private static readonly fenceCommands = new Set(['change', 'close', 'open', 'updateOpen']); + + private static getQueueingType( + command: string, + lowPriority?: boolean + ): RequestQueueingType { + if (ProcessBasedTsServer.fenceCommands.has(command)) { + return RequestQueueingType.Fence; + } + return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal; } - return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal; } + +export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServer { + public constructor( + private readonly syntaxServer: ITypeScriptServer, + private readonly semanticServer: ITypeScriptServer, + ) { + super(); + + this._register(syntaxServer.onEvent(e => this._onEvent.fire(e))); + this._register(semanticServer.onEvent(e => this._onEvent.fire(e))); + + this._register(semanticServer.onExit(e => this._onExit.fire(e))); + this._register(semanticServer.onError(e => this._onError.fire(e))); + } + + private readonly _onEvent = this._register(new vscode.EventEmitter()); + public readonly onEvent = this._onEvent.event; + + private readonly _onExit = this._register(new vscode.EventEmitter()); + public readonly onExit = this._onExit.event; + + private readonly _onError = this._register(new vscode.EventEmitter()); + public readonly onError = this._onError.event; + + public get onReaderError() { return this.semanticServer.onReaderError; } + + public get tsServerLogFile() { return this.semanticServer.tsServerLogFile; } + + public kill(): void { + this.syntaxServer.kill(); + this.semanticServer.kill(); + } + + private static readonly syntaxCommands = new Set([ + 'navtree', + 'getOutliningSpans', + 'jsxClosingTag', + 'selectionRange', + 'format', + 'formatonkey', + 'docCommentTemplate', + ]); + private static readonly sharedCommands = new Set([ + 'change', + 'close', + 'open', + 'updateOpen', + 'configure', + 'configurePlugin', + ]); + + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; + public executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { + if (SyntaxRoutingTsServer.syntaxCommands.has(command)) { + return this.syntaxServer.executeImpl(command, args, executeInfo); + } else if (SyntaxRoutingTsServer.sharedCommands.has(command)) { + // Dispatch to both server but only return from syntax one + + // Also make sure we never cancel requests to just one server + let hasCompletedSyntax = false; + let hasCompletedSemantic = false; + let token: vscode.CancellationToken | undefined = undefined; + if (executeInfo.token) { + const source = new vscode.CancellationTokenSource(); + executeInfo.token.onCancellationRequested(() => { + if (hasCompletedSyntax && !hasCompletedSemantic || hasCompletedSemantic && !hasCompletedSyntax) { + // Don't cancel. + // One of the servers completed this request so we don't want to leave the other + // in a different state + return; + } + source.cancel(); + }); + token = source.token; + } + + const semanticRequest = this.semanticServer.executeImpl(command, args, { ...executeInfo, token }); + if (semanticRequest) { + semanticRequest.finally(() => { hasCompletedSemantic = true; }); + } + const syntaxRequest = this.syntaxServer.executeImpl(command, args, { ...executeInfo, token }); + if (syntaxRequest) { + syntaxRequest.finally(() => { hasCompletedSyntax = true; }); + } + return syntaxRequest; + } else { + return this.semanticServer.executeImpl(command, args, executeInfo); + } + } +} diff --git a/extensions/typescript-language-features/src/tsServer/serverError.ts b/extensions/typescript-language-features/src/tsServer/serverError.ts new file mode 100644 index 00000000000..cb5cb8035f1 --- /dev/null +++ b/extensions/typescript-language-features/src/tsServer/serverError.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as Proto from '../protocol'; +import { escapeRegExp } from '../utils/regexp'; +import { TypeScriptVersion } from '../utils/versionProvider'; + +export class TypeScriptServerError extends Error { + public static create( + serverId: string, + version: TypeScriptVersion, + response: Proto.Response + ): TypeScriptServerError { + const parsedResult = TypeScriptServerError.parseErrorText(version, response); + return new TypeScriptServerError(serverId, version, response, parsedResult ? parsedResult.message : undefined, parsedResult ? parsedResult.stack : undefined); + } + + private constructor( + serverId: string, + version: TypeScriptVersion, + private readonly response: Proto.Response, + public readonly serverMessage: string | undefined, + public readonly serverStack: string | undefined + ) { + super(`<${serverId}> TypeScript Server Error (${version.versionString})\n${serverMessage}\n${serverStack}`); + } + + public get serverErrorText() { return this.response.message; } + + public get serverCommand() { return this.response.command; } + + /** + * Given a `errorText` from a tsserver request indicating failure in handling a request, + * prepares a payload for telemetry-logging. + */ + private static parseErrorText(version: TypeScriptVersion, response: Proto.Response) { + const errorText = response.message; + if (errorText) { + const errorPrefix = 'Error processing request. '; + if (errorText.startsWith(errorPrefix)) { + const prefixFreeErrorText = errorText.substr(errorPrefix.length); + const newlineIndex = prefixFreeErrorText.indexOf('\n'); + if (newlineIndex >= 0) { + // Newline expected between message and stack. + return { + message: prefixFreeErrorText.substring(0, newlineIndex), + stack: TypeScriptServerError.normalizeMessageStack(version, prefixFreeErrorText.substring(newlineIndex + 1)) + }; + } + } + } + return undefined; + } + + /** + * Try to replace full TS Server paths with 'tsserver.js' so that we don't have to post process the data as much + */ + private static normalizeMessageStack(version: TypeScriptVersion, message: string | undefined) { + if (!message) { + return ''; + } + return message.replace(new RegExp(`${escapeRegExp(version.path)}[/\\\\]tsserver.js:`, 'gi'), 'tsserver.js:'); + } +} diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts new file mode 100644 index 00000000000..599abe32ca1 --- /dev/null +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -0,0 +1,229 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as child_process from 'child_process'; +import * as path from 'path'; +import * as stream from 'stream'; +import * as vscode from 'vscode'; +import * as Proto from '../protocol'; +import API from '../utils/api'; +import { TsServerLogLevel, TypeScriptServiceConfiguration } from '../utils/configuration'; +import * as electron from '../utils/electron'; +import LogDirectoryProvider from '../utils/logDirectoryProvider'; +import Logger from '../utils/logger'; +import { TypeScriptPluginPathsProvider } from '../utils/pluginPathsProvider'; +import { PluginManager } from '../utils/plugins'; +import TelemetryReporter from '../utils/telemetry'; +import Tracer from '../utils/tracer'; +import { TypeScriptVersion, TypeScriptVersionProvider } from '../utils/versionProvider'; +import { ITypeScriptServer, PipeRequestCanceller, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerProcess } from './server'; + +type ServerKind = 'main' | 'syntax' | 'semantic'; + +export class TypeScriptServerSpawner { + public constructor( + private readonly _versionProvider: TypeScriptVersionProvider, + private readonly _logDirectoryProvider: LogDirectoryProvider, + private readonly _pluginPathsProvider: TypeScriptPluginPathsProvider, + private readonly _logger: Logger, + private readonly _telemetryReporter: TelemetryReporter, + private readonly _tracer: Tracer, + ) { } + + public spawn( + version: TypeScriptVersion, + configuration: TypeScriptServiceConfiguration, + pluginManager: PluginManager + ): ITypeScriptServer { + if (this.shouldUseSeparateSyntaxServer(version, configuration)) { + const syntaxServer = this.spawnTsServer('syntax', version, configuration, pluginManager); + const semanticServer = this.spawnTsServer('semantic', version, configuration, pluginManager); + return new SyntaxRoutingTsServer(syntaxServer, semanticServer); + } + + return this.spawnTsServer('main', version, configuration, pluginManager); + } + + private shouldUseSeparateSyntaxServer( + version: TypeScriptVersion, + configuration: TypeScriptServiceConfiguration, + ): boolean { + return configuration.useSeparateSyntaxServer && !!version.apiVersion && version.apiVersion.gte(API.v340); + } + + private spawnTsServer( + kind: ServerKind, + version: TypeScriptVersion, + configuration: TypeScriptServiceConfiguration, + pluginManager: PluginManager, + ): ITypeScriptServer { + const apiVersion = version.apiVersion || API.defaultVersion; + + const { args, cancellationPipeName, tsServerLogFile } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager); + + if (TypeScriptServerSpawner.isLoggingEnabled(apiVersion, configuration)) { + if (tsServerLogFile) { + this._logger.info(`<${kind}> Log file: ${tsServerLogFile}`); + } else { + this._logger.error(`<${kind}> Could not create log directory`); + } + } + + this._logger.info(`<${kind}> Forking...`); + const childProcess = electron.fork(version.tsServerPath, args, this.getForkOptions(kind)); + this._logger.info(`<${kind}> Starting...`); + + return new ProcessBasedTsServer( + kind, + new ChildServerProcess(childProcess), + tsServerLogFile, + new PipeRequestCanceller(kind, cancellationPipeName, this._tracer), + version, + this._telemetryReporter, + this._tracer); + } + + private getForkOptions(kind: ServerKind) { + const debugPort = TypeScriptServerSpawner.getDebugPort(kind); + const tsServerForkOptions: electron.ForkOptions = { + execArgv: debugPort ? [`--inspect=${debugPort}`] : [], + }; + return tsServerForkOptions; + } + + private getTsServerArgs( + kind: ServerKind, + configuration: TypeScriptServiceConfiguration, + currentVersion: TypeScriptVersion, + apiVersion: API, + pluginManager: PluginManager, + ): { args: string[], cancellationPipeName: string | undefined, tsServerLogFile: string | undefined } { + const args: string[] = []; + let cancellationPipeName: string | undefined; + let tsServerLogFile: string | undefined; + + if (kind === 'syntax') { + args.push('--syntaxOnly'); + } + + if (apiVersion.gte(API.v206)) { + if (apiVersion.gte(API.v250)) { + args.push('--useInferredProjectPerProjectRoot'); + } else { + args.push('--useSingleInferredProject'); + } + + if (configuration.disableAutomaticTypeAcquisition || kind === 'syntax') { + args.push('--disableAutomaticTypingAcquisition'); + } + } + + if (apiVersion.gte(API.v208) && kind !== 'syntax') { + args.push('--enableTelemetry'); + } + + if (apiVersion.gte(API.v222)) { + cancellationPipeName = electron.getTempFile('tscancellation'); + args.push('--cancellationPipeName', cancellationPipeName + '*'); + } + + if (TypeScriptServerSpawner.isLoggingEnabled(apiVersion, configuration)) { + const logDir = this._logDirectoryProvider.getNewLogDirectory(); + if (logDir) { + tsServerLogFile = path.join(logDir, `tsserver.log`); + args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel)); + args.push('--logFile', tsServerLogFile); + } + } + + if (apiVersion.gte(API.v230)) { + const pluginPaths = this._pluginPathsProvider.getPluginPaths(); + + if (pluginManager.plugins.length) { + args.push('--globalPlugins', pluginManager.plugins.map(x => x.name).join(',')); + + const isUsingBundledTypeScriptVersion = currentVersion.path === this._versionProvider.defaultVersion.path; + for (const plugin of pluginManager.plugins) { + if (isUsingBundledTypeScriptVersion || plugin.enableForWorkspaceTypeScriptVersions) { + pluginPaths.push(plugin.path); + } + } + } + + if (pluginPaths.length !== 0) { + args.push('--pluginProbeLocations', pluginPaths.join(',')); + } + } + + if (apiVersion.gte(API.v234)) { + if (configuration.npmLocation) { + args.push('--npmLocation', `"${configuration.npmLocation}"`); + } + } + + if (apiVersion.gte(API.v260)) { + args.push('--locale', TypeScriptServerSpawner.getTsLocale(configuration)); + } + + if (apiVersion.gte(API.v291)) { + args.push('--noGetErrOnBackgroundUpdate'); + } + + if (apiVersion.gte(API.v345)) { + args.push('--validateDefaultNpmLocation'); + } + + return { args, cancellationPipeName, tsServerLogFile }; + } + + private static getDebugPort(kind: ServerKind): number | undefined { + if (kind === 'syntax') { + // We typically only want to debug the main semantic server + return undefined; + } + const value = process.env['TSS_DEBUG']; + if (value) { + const port = parseInt(value); + if (!isNaN(port)) { + return port; + } + } + return undefined; + } + + private static isLoggingEnabled(apiVersion: API, configuration: TypeScriptServiceConfiguration) { + return apiVersion.gte(API.v222) && + configuration.tsServerLogLevel !== TsServerLogLevel.Off; + } + + private static getTsLocale(configuration: TypeScriptServiceConfiguration): string { + return configuration.locale + ? configuration.locale + : vscode.env.language; + } +} + +class ChildServerProcess implements TsServerProcess { + + public constructor( + private readonly _process: child_process.ChildProcess, + ) { } + + get stdout(): stream.Readable { return this._process.stdout!; } + + write(serverRequest: Proto.Request): void { + this._process.stdin!.write(JSON.stringify(serverRequest) + '\r\n', 'utf8'); + } + + on(name: 'exit', handler: (code: number | null) => void): void; + on(name: 'error', handler: (error: Error) => void): void; + on(name: any, handler: any) { + this._process.on(name, handler); + } + + kill(): void { + this._process.kill(); + } +} \ No newline at end of file diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 1156b79d268..ba396ad2fd2 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -26,7 +26,7 @@ export namespace ServerResponse { export type Response = T | Cancelled | typeof NoContent; } -export interface TypeScriptRequestTypes { +interface StandardTsServerRequests { 'applyCodeActionCommand': [Proto.ApplyCodeActionCommandRequestArgs, Proto.ApplyCodeActionCommandResponse]; 'completionEntryDetails': [Proto.CompletionDetailsRequestArgs, Proto.CompletionDetailsResponse]; 'completionInfo': [Proto.CompletionsRequestArgs, Proto.CompletionInfoResponse]; @@ -59,6 +59,26 @@ export interface TypeScriptRequestTypes { 'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse]; } +interface NoResponseTsServerRequests { + 'open': [Proto.OpenRequestArgs, null]; + 'close': [Proto.FileRequestArgs]; + 'change': [Proto.ChangeRequestArgs, null]; + 'updateOpen': [Proto.UpdateOpenRequestArgs, null]; + 'compilerOptionsForInferredProjects': [Proto.SetCompilerOptionsForInferredProjectsArgs, null]; + 'reloadProjects': [null, null]; + 'configurePlugin': [Proto.ConfigurePluginRequest, Proto.ConfigurePluginResponse]; +} + +interface AsyncTsServerRequests { + 'geterr': [Proto.GeterrRequestArgs, Proto.Response]; +} + +export type TypeScriptRequests = StandardTsServerRequests & NoResponseTsServerRequests & AsyncTsServerRequests; + +export type ExecConfig = { + lowPriority?: boolean; +}; + export interface ITypeScriptServiceClient { /** * Convert a resource (VS Code) to a normalized path (TypeScript). @@ -100,19 +120,17 @@ export interface ITypeScriptServiceClient { readonly logger: Logger; readonly bufferSyncSupport: BufferSyncSupport; - execute( + execute( command: K, - args: TypeScriptRequestTypes[K][0], + args: StandardTsServerRequests[K][0], token: vscode.CancellationToken, - lowPriority?: boolean - ): Promise>; + config?: ExecConfig + ): Promise>; - executeWithoutWaitingForResponse(command: 'open', args: Proto.OpenRequestArgs): void; - executeWithoutWaitingForResponse(command: 'close', args: Proto.FileRequestArgs): void; - executeWithoutWaitingForResponse(command: 'change', args: Proto.ChangeRequestArgs): void; - executeWithoutWaitingForResponse(command: 'updateOpen', args: Proto.UpdateOpenRequestArgs): void; - executeWithoutWaitingForResponse(command: 'compilerOptionsForInferredProjects', args: Proto.SetCompilerOptionsForInferredProjectsArgs): void; - executeWithoutWaitingForResponse(command: 'reloadProjects', args: null): void; + executeWithoutWaitingForResponse( + command: K, + args: NoResponseTsServerRequests[K][0] + ): void; executeAsync(command: 'geterr', args: Proto.GeterrRequestArgs, token: vscode.CancellationToken): Promise>; diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index b478e9ed080..996478d6c22 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -10,8 +10,8 @@ import * as nls from 'vscode-nls'; import BufferSyncSupport from './features/bufferSyncSupport'; import { DiagnosticKind, DiagnosticsManager } from './features/diagnostics'; import * as Proto from './protocol'; -import { TypeScriptServer, TypeScriptServerSpawner } from './tsServer/server'; -import { ITypeScriptServiceClient, ServerResponse } from './typescriptService'; +import { ITypeScriptServer } from './tsServer/server'; +import { ITypeScriptServiceClient, ServerResponse, TypeScriptRequests, ExecConfig } from './typescriptService'; import API from './utils/api'; import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration'; import { Disposable } from './utils/dispose'; @@ -25,6 +25,7 @@ import Tracer from './utils/tracer'; import { inferredProjectConfig } from './utils/tsconfig'; import { TypeScriptVersionPicker } from './utils/versionPicker'; import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider'; +import { TypeScriptServerSpawner } from './tsServer/spawner'; const localize = nls.loadMessageBundle(); @@ -46,7 +47,7 @@ namespace ServerState { export class Running { readonly type = Type.Running; constructor( - public readonly server: TypeScriptServer, + public readonly server: ITypeScriptServer, /** * API version obtained from the version picker after checking the corresponding path exists. @@ -284,7 +285,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType currentVersion = this.versionPicker.currentVersion; } - const apiVersion = this.versionPicker.currentVersion.version || API.defaultVersion; + const apiVersion = this.versionPicker.currentVersion.apiVersion || API.defaultVersion; this.onDidChangeTypeScriptVersion(currentVersion); let mytoken = ++this.token; @@ -352,7 +353,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType handle.onEvent(event => this.dispatchEvent(event)); this._onReady!.resolve(); - this._onTsServerStarted.fire(currentVersion.version); + this._onTsServerStarted.fire(currentVersion.apiVersion); if (apiVersion.gte(API.v300)) { this.loadingIndicator.startedLoadingProject(undefined /* projectName */); @@ -606,16 +607,16 @@ export default class TypeScriptServiceClient extends Disposable implements IType return undefined; } - public execute(command: string, args: any, token: vscode.CancellationToken, lowPriority?: boolean): Promise> { + public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise> { return this.executeImpl(command, args, { isAsync: false, token, expectsResult: true, - lowPriority + lowPriority: config ? config.lowPriority : undefined }); } - public executeWithoutWaitingForResponse(command: string, args: any): void { + public executeWithoutWaitingForResponse(command: keyof TypeScriptRequests, args: any): void { this.executeImpl(command, args, { isAsync: false, token: undefined, @@ -623,7 +624,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType }); } - public executeAsync(command: string, args: Proto.GeterrRequestArgs, token: vscode.CancellationToken): Promise> { + public executeAsync(command: keyof TypeScriptRequests, args: Proto.GeterrRequestArgs, token: vscode.CancellationToken): Promise> { return this.executeImpl(command, args, { isAsync: true, token, @@ -631,9 +632,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType }); } - private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; - private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; - private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { + private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined; + private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise>; + private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { this.bufferSyncSupport.beforeCommand(command); const runningServerState = this.service(); return runningServerState.server.executeImpl(command, args, executeInfo); @@ -768,7 +769,6 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.logTelemetry(telemetryData.telemetryEventName, properties); } - private configurePlugin(pluginName: string, configuration: {}): any { if (this.apiVersion.gte(API.v314)) { this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration }); diff --git a/extensions/typescript-language-features/src/utils/configuration.ts b/extensions/typescript-language-features/src/utils/configuration.ts index 3babc53c91b..97619282256 100644 --- a/extensions/typescript-language-features/src/utils/configuration.ts +++ b/extensions/typescript-language-features/src/utils/configuration.ts @@ -54,6 +54,7 @@ export class TypeScriptServiceConfiguration { public readonly checkJs: boolean; public readonly experimentalDecorators: boolean; public readonly disableAutomaticTypeAcquisition: boolean; + public readonly useSeparateSyntaxServer: boolean; public static loadFromWorkspace(): TypeScriptServiceConfiguration { return new TypeScriptServiceConfiguration(); @@ -71,6 +72,7 @@ export class TypeScriptServiceConfiguration { this.checkJs = TypeScriptServiceConfiguration.readCheckJs(configuration); this.experimentalDecorators = TypeScriptServiceConfiguration.readExperimentalDecorators(configuration); this.disableAutomaticTypeAcquisition = TypeScriptServiceConfiguration.readDisableAutomaticTypeAcquisition(configuration); + this.useSeparateSyntaxServer = TypeScriptServiceConfiguration.readUseSeparateSyntaxServer(configuration); } public isEqualTo(other: TypeScriptServiceConfiguration): boolean { @@ -82,7 +84,8 @@ export class TypeScriptServiceConfiguration { && this.checkJs === other.checkJs && this.experimentalDecorators === other.experimentalDecorators && this.disableAutomaticTypeAcquisition === other.disableAutomaticTypeAcquisition - && arrays.equals(this.tsServerPluginPaths, other.tsServerPluginPaths); + && arrays.equals(this.tsServerPluginPaths, other.tsServerPluginPaths) + && this.useSeparateSyntaxServer === other.useSeparateSyntaxServer; } private static fixPathPrefixes(inspectValue: string): string { @@ -139,4 +142,8 @@ export class TypeScriptServiceConfiguration { private static extractLocale(configuration: vscode.WorkspaceConfiguration): string | null { return configuration.get('typescript.locale', null); } + + private static readUseSeparateSyntaxServer(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('typescript.experimental.useSeparateSyntaxServer', false); + } } diff --git a/extensions/typescript-language-features/src/utils/electron.ts b/extensions/typescript-language-features/src/utils/electron.ts index 3a1ece8f726..ea16bae4277 100644 --- a/extensions/typescript-language-features/src/utils/electron.ts +++ b/extensions/typescript-language-features/src/utils/electron.ts @@ -7,13 +7,14 @@ import * as temp from './temp'; import path = require('path'); import fs = require('fs'); import cp = require('child_process'); +import process = require('process'); const getRootTempDir = (() => { let dir: string | undefined; return () => { if (!dir) { - dir = temp.getTempFile(`vscode-typescript`); + dir = temp.getTempFile(`vscode-typescript${process.platform !== 'win32' && process.getuid ? process.getuid() : ''}`); } if (!fs.existsSync(dir)) { fs.mkdirSync(dir); diff --git a/extensions/typescript-language-features/src/utils/pluginPathsProvider.ts b/extensions/typescript-language-features/src/utils/pluginPathsProvider.ts index 547660f0649..de6d03cd980 100644 --- a/extensions/typescript-language-features/src/utils/pluginPathsProvider.ts +++ b/extensions/typescript-language-features/src/utils/pluginPathsProvider.ts @@ -9,7 +9,6 @@ import { RelativeWorkspacePathResolver } from './relativePathResolver'; export class TypeScriptPluginPathsProvider { - public readonly relativePathResolver: RelativeWorkspacePathResolver = new RelativeWorkspacePathResolver(); public constructor( private configuration: TypeScriptServiceConfiguration @@ -32,7 +31,7 @@ export class TypeScriptPluginPathsProvider { return [pluginPath]; } - const workspacePath = this.relativePathResolver.asAbsoluteWorkspacePath(pluginPath); + const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(pluginPath); if (workspacePath !== undefined) { return [workspacePath]; } diff --git a/extensions/typescript-language-features/src/utils/relativePathResolver.ts b/extensions/typescript-language-features/src/utils/relativePathResolver.ts index 85b72a55d6b..64dff66fb53 100644 --- a/extensions/typescript-language-features/src/utils/relativePathResolver.ts +++ b/extensions/typescript-language-features/src/utils/relativePathResolver.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; export class RelativeWorkspacePathResolver { - public asAbsoluteWorkspacePath(relativePath: string): string | undefined { + public static asAbsoluteWorkspacePath(relativePath: string): string | undefined { for (const root of vscode.workspace.workspaceFolders || []) { const rootPrefixes = [`./${root.name}/`, `${root.name}/`, `.\\${root.name}\\`, `${root.name}\\`]; for (const rootPrefix of rootPrefixes) { diff --git a/extensions/typescript-language-features/src/utils/tracer.ts b/extensions/typescript-language-features/src/utils/tracer.ts index c80254c79a0..f7ca0d674e3 100644 --- a/extensions/typescript-language-features/src/utils/tracer.ts +++ b/extensions/typescript-language-features/src/utils/tracer.ts @@ -50,7 +50,7 @@ export default class Tracer { return result; } - public traceRequest(request: Proto.Request, responseExpected: boolean, queueLength: number): void { + public traceRequest(serverId: string, request: Proto.Request, responseExpected: boolean, queueLength: number): void { if (this.trace === Trace.Off) { return; } @@ -58,10 +58,10 @@ export default class Tracer { if (this.trace === Trace.Verbose && request.arguments) { data = `Arguments: ${JSON.stringify(request.arguments, null, 4)}`; } - this.logTrace(`Sending request: ${request.command} (${request.seq}). Response expected: ${responseExpected ? 'yes' : 'no'}. Current queue length: ${queueLength}`, data); + this.logTrace(serverId, `Sending request: ${request.command} (${request.seq}). Response expected: ${responseExpected ? 'yes' : 'no'}. Current queue length: ${queueLength}`, data); } - public traceResponse(response: Proto.Response, startTime: number): void { + public traceResponse(serverId: string, response: Proto.Response, startTime: number): void { if (this.trace === Trace.Off) { return; } @@ -69,17 +69,17 @@ export default class Tracer { if (this.trace === Trace.Verbose && response.body) { data = `Result: ${JSON.stringify(response.body, null, 4)}`; } - this.logTrace(`Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - startTime} ms. Success: ${response.success} ${!response.success ? '. Message: ' + response.message : ''}`, data); + this.logTrace(serverId, `Response received: ${response.command} (${response.request_seq}). Request took ${Date.now() - startTime} ms. Success: ${response.success} ${!response.success ? '. Message: ' + response.message : ''}`, data); } - public traceRequestCompleted(command: string, request_seq: number, startTime: number): any { + public traceRequestCompleted(serverId: string, command: string, request_seq: number, startTime: number): any { if (this.trace === Trace.Off) { return; } - this.logTrace(`Async response received: ${command} (${request_seq}). Request took ${Date.now() - startTime} ms.`); + this.logTrace(serverId, `Async response received: ${command} (${request_seq}). Request took ${Date.now() - startTime} ms.`); } - public traceEvent(event: Proto.Event): void { + public traceEvent(serverId: string, event: Proto.Event): void { if (this.trace === Trace.Off) { return; } @@ -87,12 +87,12 @@ export default class Tracer { if (this.trace === Trace.Verbose && event.body) { data = `Data: ${JSON.stringify(event.body, null, 4)}`; } - this.logTrace(`Event received: ${event.event} (${event.seq}).`, data); + this.logTrace(serverId, `Event received: ${event.event} (${event.seq}).`, data); } - public logTrace(message: string, data?: any): void { + public logTrace(serverId: string, message: string, data?: any): void { if (this.trace !== Trace.Off) { - this.logger.logLevel('Trace', message, data); + this.logger.logLevel('Trace', `<${serverId}> ${message}`, data); } } } \ No newline at end of file diff --git a/extensions/typescript-language-features/src/utils/typeConverters.ts b/extensions/typescript-language-features/src/utils/typeConverters.ts index eaa6a96c860..37947b38810 100644 --- a/extensions/typescript-language-features/src/utils/typeConverters.ts +++ b/extensions/typescript-language-features/src/utils/typeConverters.ts @@ -13,9 +13,12 @@ import { ITypeScriptServiceClient } from '../typescriptService'; export namespace Range { export const fromTextSpan = (span: Proto.TextSpan): vscode.Range => + fromLocations(span.start, span.end); + + export const fromLocations = (start: Proto.Location, end: Proto.Location): vscode.Range => new vscode.Range( - Math.max(0, span.start.line - 1), Math.max(span.start.offset - 1, 0), - Math.max(0, span.end.line - 1), Math.max(0, span.end.offset - 1)); + Math.max(0, start.line - 1), Math.max(start.offset - 1, 0), + Math.max(0, end.line - 1), Math.max(0, end.offset - 1)); export const toFileRangeRequestArgs = (file: string, range: vscode.Range): Proto.FileRangeRequestArgs => ({ file, diff --git a/extensions/typescript-language-features/src/utils/versionPicker.ts b/extensions/typescript-language-features/src/utils/versionPicker.ts index afbda6ef206..d1827002ee3 100644 --- a/extensions/typescript-language-features/src/utils/versionPicker.ts +++ b/extensions/typescript-language-features/src/utils/versionPicker.ts @@ -113,7 +113,7 @@ export class TypeScriptVersionPicker { return { oldVersion: previousVersion, newVersion: shippedVersion }; case MessageAction.learnMore: - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919')); + vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919')); return { oldVersion: this.currentVersion }; default: diff --git a/extensions/typescript-language-features/src/utils/versionProvider.ts b/extensions/typescript-language-features/src/utils/versionProvider.ts index fcbee87bb1d..8e9e9a2a8c6 100644 --- a/extensions/typescript-language-features/src/utils/versionProvider.ts +++ b/extensions/typescript-language-features/src/utils/versionProvider.ts @@ -27,10 +27,10 @@ export class TypeScriptVersion { } public get isValid(): boolean { - return this.version !== undefined; + return this.apiVersion !== undefined; } - public get version(): API | undefined { + public get apiVersion(): API | undefined { const version = this.getTypeScriptVersion(this.tsServerPath); if (version) { return version; @@ -46,7 +46,7 @@ export class TypeScriptVersion { } public get versionString(): string { - const version = this.version; + const version = this.apiVersion; return version ? version.versionString : localize( 'couldNotLoadTsVersion', 'Could not load the TypeScript version at this path'); } @@ -88,7 +88,6 @@ export class TypeScriptVersion { } export class TypeScriptVersionProvider { - private readonly relativePathResolver: RelativeWorkspacePathResolver = new RelativeWorkspacePathResolver(); public constructor( private configuration: TypeScriptServiceConfiguration @@ -179,7 +178,7 @@ export class TypeScriptVersionProvider { return [new TypeScriptVersion(tsdkPathSetting)]; } - const workspacePath = this.relativePathResolver.asAbsoluteWorkspacePath(tsdkPathSetting); + const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(tsdkPathSetting); if (workspacePath !== undefined) { return [new TypeScriptVersion(workspacePath, tsdkPathSetting)]; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts index 31d6d4ce312..6cfc6f60408 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/env.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { env } from 'vscode'; +import { env, extensions, ExtensionKind } from 'vscode'; suite('env-namespace', () => { @@ -24,4 +24,22 @@ suite('env-namespace', () => { assert.throws(() => (env as any).sessionId = '234'); }); + test('env.remoteName', function () { + const remoteName = env.remoteName; + const apiTestExtension = extensions.getExtension('vscode.vscode-api-tests'); + const testResolverExtension = extensions.getExtension('vscode.vscode-test-resolver'); + if (typeof remoteName === 'undefined') { + assert.ok(apiTestExtension); + assert.ok(testResolverExtension); + assert.equal(ExtensionKind.UI, apiTestExtension!.extensionKind); + assert.equal(ExtensionKind.UI, testResolverExtension!.extensionKind); + } else if (typeof remoteName === 'string') { + assert.ok(apiTestExtension); + assert.ok(!testResolverExtension); // we currently can only access extensions that run on same host + assert.equal(ExtensionKind.Workspace, apiTestExtension!.extensionKind); + } else { + assert.fail(); + } + }); + }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 3cc1cb3863b..8ef8cec74d6 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -251,7 +251,7 @@ suite('Webview tests', () => { }); `); - const workspaceRootUri = vscode.Uri.file(vscode.workspace.rootPath!).with({ scheme: 'vscode-resource' }); + const workspaceRootUri = webview.webview.resourceRoot + vscode.Uri.file(vscode.workspace.rootPath!).path; { const imagePath = workspaceRootUri.toString() + '/image.png'; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 4218162c24d..19184284108 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -569,7 +569,7 @@ suite('window namespace tests', () => { }); }); - suite('Terminal', () => { + (process.platform === 'win32' ? suite.skip /* https://github.com/microsoft/vscode/issues/75689 */ : suite)('Terminal', () => { test('sendText immediately after createTerminal should not throw', () => { const terminal = window.createTerminal(); assert.doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"')); @@ -738,8 +738,8 @@ suite('window namespace tests', () => { terminal1.show(); }); - test('runInBackground terminal: onDidWriteData should work', done => { - const terminal = window.createTerminal({ name: 'bg', runInBackground: true }); + test('hideFromUser terminal: onDidWriteData should work', done => { + const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); let data = ''; terminal.onDidWriteData(e => { data += e; @@ -751,8 +751,8 @@ suite('window namespace tests', () => { terminal.sendText('foo'); }); - test('runInBackground terminal: should be available to terminals API', done => { - const terminal = window.createTerminal({ name: 'bg', runInBackground: true }); + test('hideFromUser terminal: should be available to terminals API', done => { + const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); window.onDidOpenTerminal(t => { assert.equal(t, terminal); assert.equal(t.name, 'bg'); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts new file mode 100644 index 00000000000..cd82f10c17d --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { posix } from 'path'; + +suite('workspace-fs', () => { + + let root: vscode.Uri; + + suiteSetup(function () { + root = vscode.workspace.workspaceFolders![0]!.uri; + }); + + test('fs.stat', async function () { + const stat = await vscode.workspace.fs.stat(root); + assert.equal(stat.type, vscode.FileType.Directory); + + assert.equal(typeof stat.size, 'number'); + assert.equal(typeof stat.mtime, 'number'); + assert.equal(typeof stat.ctime, 'number'); + + + const entries = await vscode.workspace.fs.readDirectory(root); + assert.ok(entries.length > 0); + + // find far.js + const tuple = entries.find(tuple => tuple[0] === 'far.js')!; + assert.ok(tuple); + assert.equal(tuple[0], 'far.js'); + assert.equal(tuple[1], vscode.FileType.File); + }); + + test('fs.stat - bad scheme', async function () { + try { + await vscode.workspace.fs.stat(vscode.Uri.parse('foo:/bar/baz/test.txt')); + assert.ok(false); + } catch { + assert.ok(true); + } + }); + + test('fs.stat - missing file', async function () { + try { + await vscode.workspace.fs.stat(root.with({ path: root.path + '.bad' })); + assert.ok(false); + } catch (e) { + assert.ok(true); + } + }); + + test('fs.write/stat/delete', async function () { + + const uri = root.with({ path: posix.join(root.path, 'new.file') }); + await vscode.workspace.fs.writeFile(uri, Buffer.from('HELLO')); + + const stat = await vscode.workspace.fs.stat(uri); + assert.equal(stat.type, vscode.FileType.File); + + await vscode.workspace.fs.delete(uri); + + try { + await vscode.workspace.fs.stat(uri); + assert.ok(false); + } catch { + assert.ok(true); + } + }); + + test('fs.delete folder', async function () { + + const folder = root.with({ path: posix.join(root.path, 'folder') }); + const file = root.with({ path: posix.join(root.path, 'folder/file') }); + + await vscode.workspace.fs.createDirectory(folder); + await vscode.workspace.fs.writeFile(file, Buffer.from('FOO')); + + await vscode.workspace.fs.stat(folder); + await vscode.workspace.fs.stat(file); + + // ensure non empty folder cannot be deleted + try { + await vscode.workspace.fs.delete(folder, { recursive: false }); + assert.ok(false); + } catch { + await vscode.workspace.fs.stat(folder); + await vscode.workspace.fs.stat(file); + } + + // ensure non empty folder cannot be deleted is DEFAULT + try { + await vscode.workspace.fs.delete(folder); // recursive: false as default + assert.ok(false); + } catch { + await vscode.workspace.fs.stat(folder); + await vscode.workspace.fs.stat(file); + } + + // delete non empty folder with recursive-flag + await vscode.workspace.fs.delete(folder, { recursive: true }); + + // esnure folder/file are gone + try { + await vscode.workspace.fs.stat(folder); + assert.ok(false); + } catch { + assert.ok(true); + } + try { + await vscode.workspace.fs.stat(file); + assert.ok(false); + } catch { + assert.ok(true); + } + }); +}); diff --git a/package.json b/package.json index 4176b32d5cb..96d94efdecf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.36.0", - "distro": "0293a04fc4ec308a7770025ead5660e2fb9667a9", + "version": "1.37.0", + "distro": "b49e556c274bfe2fc7f4fafd3804cb4ebd948c4f", "author": { "name": "Microsoft Corporation" }, @@ -24,10 +24,7 @@ "smoketest": "cd test/smoke && node test/index.js", "download-builtin-extensions": "node build/lib/builtInExtensions.js", "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", - "strict-initialization-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictPropertyInitialization", - "web": "node resources/server/bin-dev/code-web.js --port 9888", - "web-selfhost": "node resources/server/bin-dev/code-web.js --port 9777 --selfhost --folder .", - "install-server": "node build/npm/install-server.js" + "strict-initialization-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictPropertyInitialization" }, "dependencies": { "applicationinsights": "1.0.8", @@ -40,7 +37,7 @@ "keytar": "4.2.1", "minimist": "1.2.0", "native-is-elevated": "^0.2.1", - "native-keymap": "1.2.5", + "native-keymap": "1.2.6", "native-watchdog": "1.0.0", "node-pty": "0.9.0-beta17", "onigasm-umd": "^2.2.2", @@ -54,7 +51,7 @@ "vscode-ripgrep": "^1.2.5", "vscode-sqlite3": "4.0.7", "vscode-textmate": "^4.1.1", - "xterm": "3.15.0-beta42", + "xterm": "3.15.0-beta50", "xterm-addon-search": "0.1.0-beta6", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", @@ -79,7 +76,7 @@ "debounce": "^1.0.0", "documentdb": "^1.5.1", "electron-mksnapshot": "~2.0.0", - "eslint": "^3.4.0", + "eslint": "^4.18.2", "event-stream": "3.3.4", "express": "^4.13.1", "fancy-log": "^1.3.3", @@ -108,7 +105,7 @@ "gulp-vinyl-zip": "^2.1.2", "http-server": "^0.11.1", "husky": "^0.13.1", - "innosetup-compiler": "^5.5.60", + "innosetup": "5.6.1", "is": "^3.1.0", "istanbul": "^0.3.17", "jsdom-no-contextify": "^3.1.0", diff --git a/remote/package.json b/remote/package.json index da17913e867..7a48c4cb238 100644 --- a/remote/package.json +++ b/remote/package.json @@ -12,7 +12,7 @@ "keytar": "4.2.1", "minimist": "1.2.0", "native-watchdog": "1.0.0", - "node-pty": "0.8.1", + "node-pty": "0.9.0-beta17", "onigasm-umd": "^2.2.2", "semver": "^5.5.0", "spdlog": "^0.9.0", @@ -21,13 +21,14 @@ "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.2.5", "vscode-textmate": "^4.1.1", - "yauzl": "^2.9.2", - "yazl": "^2.4.3", "xterm": "3.15.0-beta34", "xterm-addon-search": "0.1.0-beta6", - "xterm-addon-web-links": "0.1.0-beta10" + "xterm-addon-web-links": "0.1.0-beta10", + "yauzl": "^2.9.2", + "yazl": "^2.4.3" }, "optionalDependencies": { - "vscode-windows-ca-certs": "0.1.0" + "vscode-windows-ca-certs": "0.1.0", + "vscode-windows-registry": "1.0.1" } } diff --git a/remote/yarn.lock b/remote/yarn.lock index 11db8ed16fb..6bf34b0726a 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -599,11 +599,6 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -nan@2.12.1: - version "2.12.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" - integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== - nan@2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" @@ -614,7 +609,7 @@ nan@^2.10.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" integrity sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw== -nan@^2.12.1, nan@^2.14.0: +nan@^2.12.1, nan@^2.13.2, nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -636,12 +631,12 @@ node-addon-api@1.6.2: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217" integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA== -node-pty@0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.8.1.tgz#94b457bec013e7a09b8d9141f63b0787fa25c23f" - integrity sha512-j+/g0Q5dR+vkELclpJpz32HcS3O/3EdPSGPvDXJZVJQLCvgG0toEbfmymxAEyQyZEpaoKHAcoL+PvKM+4N9nlw== +node-pty@0.9.0-beta17: + version "0.9.0-beta17" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0-beta17.tgz#9b490df86a8124dea595e9fbedeaaf4b2eedbbcb" + integrity sha512-E94XwIs3JxLKAboquHY9Kytbbj/T/tJtRpQoAUdfPE7UXRta/NV+xdmRNhZkeU9jCji+plm656GbYFievgNPkQ== dependencies: - nan "2.12.1" + nan "^2.13.2" noop-logger@^0.1.1: version "0.1.1" @@ -1117,6 +1112,13 @@ vscode-windows-ca-certs@0.1.0: dependencies: node-addon-api "1.6.2" +vscode-windows-registry@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.1.tgz#bc9f765563eb6dc1c9ad9a41f9eaacc84dfadc7c" + integrity sha512-q0aKXi9Py1OBdmXIJJFeJBzpPJMMUxMJNBU9FysWIXEwJyMQGEVevKzM2J3Qz/cHSc5LVqibmoUWzZ7g+97qRg== + dependencies: + nan "^2.12.1" + which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" diff --git a/resources/completions/bash/code b/resources/completions/bash/code index e377c5d24e2..f40963db05c 100644 --- a/resources/completions/bash/code +++ b/resources/completions/bash/code @@ -50,7 +50,7 @@ _code() --uninstall-extension --enable-proposed-api --verbose --log -s --status -p --performance --prof-startup --disable-extensions --disable-extension --inspect-extensions - --inspect-brk-extensions --disable-gpu --upload-logs + --inspect-brk-extensions --disable-gpu --max-memory=' -- "$cur") ) [[ $COMPREPLY == *= ]] && compopt -o nospace return diff --git a/resources/completions/zsh/_code b/resources/completions/zsh/_code index 9579cffb2f6..054ab351e91 100644 --- a/resources/completions/zsh/_code +++ b/resources/completions/zsh/_code @@ -14,6 +14,7 @@ arguments=( '--user-data-dir[specify the directory that user data is kept in]:directory:_directories' '(- *)'{-v,--version}'[print version]' '(- *)'{-h,--help}'[print usage]' + '(- *)'{--telemetry}'[Shows all telemetry events which VS code collects.]' '--extensions-dir[set the root path for extensions]:root path:_directories' '--list-extensions[list the installed extensions]' '--show-versions[show versions of installed extensions, when using --list-extension]' @@ -30,7 +31,6 @@ arguments=( '--inspect-extensions[allow debugging and profiling of extensions]' '--inspect-brk-extensions[allow debugging and profiling of extensions with the extension host being paused after start]' '--disable-gpu[disable GPU hardware acceleration]' - '--upload-logs[upload logs from current session to a secure endpoint]:confirm:(iConfirmLogsUpload)' '--max-memory=[max memory size for a window (in Mbytes)]:size (Mbytes)' '*:file or directory:_files' ) diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index b1a0f7e4f3e..5d92383c496 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -11,8 +11,18 @@ VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")" ELECTRON="$VSCODE_PATH/$NAME.exe" if grep -qi Microsoft /proc/version; then # in a wsl shell - WSL_BUILD=$(uname -r | sed -E 's/^.+-([0-9]+)-[Mm]icrosoft/\1/') - if [ $WSL_BUILD -ge 17063 ] 2> /dev/null; then + if ! [ -z "$WSL_DISTRO_NAME" ]; then + # $WSL_DISTRO_NAME is available since WSL builds 18362, also for WSL2 + WSL_BUILD=18362 + else + WSL_BUILD=$(uname -r | sed -E 's/^.+-([0-9]+)-Microsoft/\1/') + if [ -z "$WSL_BUILD" ]; then + WSL_BUILD=0 + fi + fi + + if [ $WSL_BUILD -ge 17063 ]; then + # $WSL_DISTRO_NAME is available since WSL builds 18362, also for WSL2 # WSLPATH is available since WSL build 17046 # WSLENV is available since WSL build 17063 export WSLENV=ELECTRON_RUN_AS_NODE/w:$WSLENV diff --git a/scripts/env.ps1 b/scripts/env.ps1 deleted file mode 100644 index 72e094d0a6a..00000000000 --- a/scripts/env.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -$env:npm_config_disturl="https://atom.io/download/electron" -$env:npm_config_target=(node -p "require('./build/lib/electron').getElectronVersion();") -$env:npm_config_runtime="electron" diff --git a/scripts/env.sh b/scripts/env.sh deleted file mode 100755 index 8f641759b27..00000000000 --- a/scripts/env.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -export npm_config_disturl=https://atom.io/download/electron -export npm_config_target=$(node -p "require('./build/lib/electron').getElectronVersion();") -export npm_config_runtime=electron -export npm_config_cache="$HOME/.npm-electron" -mkdir -p "$npm_config_cache" diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index cf8902a2bce..20567bc77cf 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -10,16 +10,16 @@ call .\scripts\test.bat --runGlob **\*.integrationTest.js %* if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in the extension host -call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% -call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% -call .\scripts\code.bat %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +call .\scripts\code.bat %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% -call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% . +call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% . if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in commonJS (HTML, CSS, JSON language server tests...) diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 3d397ed204e..45595a5f0a3 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -16,13 +16,13 @@ cd $ROOT ./scripts/test.sh --runGlob **/*.integrationTest.js "$@" # Tests in the extension host -./scripts/code.sh $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-extensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started -./scripts/code.sh $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-extensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started -./scripts/code.sh $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-extensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started -./scripts/code.sh $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-extensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started +./scripts/code.sh $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +./scripts/code.sh $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +./scripts/code.sh $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +./scripts/code.sh $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR mkdir -p $ROOT/extensions/emmet/test-fixtures -./scripts/code.sh $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-extensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started +./scripts/code.sh $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR rm -rf $ROOT/extensions/emmet/test-fixtures if [ -f ./resources/server/test/test-remote-integration.sh ]; then diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index df64e7a0b28..c52a879c36f 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Electron 4.2.4 +// Type definitions for Electron 4.2.5 // Project: http://electronjs.org/ // Definitions by: The Electron Team // Definitions: https://github.com/electron/electron-typescript-definitions diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 6a0cc217897..b447e0b68ec 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -122,6 +122,7 @@ export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0)); export const isWebkitWebView = (!isChrome && !isSafari && isWebKit); export const isIPad = (userAgent.indexOf('iPad') >= 0); export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0); +export const isStandalone = (window.matchMedia('(display-mode: standalone)').matches); export function hasClipboardSupport() { if (isIE) { diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index c5f49f198e5..e201902ae24 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -38,12 +38,12 @@ export function isInDOM(node: Node | null): boolean { } interface IDomClassList { - hasClass(node: HTMLElement, className: string): boolean; - addClass(node: HTMLElement, className: string): void; - addClasses(node: HTMLElement, ...classNames: string[]): void; - removeClass(node: HTMLElement, className: string): void; - removeClasses(node: HTMLElement, ...classNames: string[]): void; - toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void; + hasClass(node: HTMLElement | SVGElement, className: string): boolean; + addClass(node: HTMLElement | SVGElement, className: string): void; + addClasses(node: HTMLElement | SVGElement, ...classNames: string[]): void; + removeClass(node: HTMLElement | SVGElement, className: string): void; + removeClasses(node: HTMLElement | SVGElement, ...classNames: string[]): void; + toggleClass(node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean): void; } const _manualClassList = new class implements IDomClassList { @@ -191,12 +191,12 @@ const _nativeClassList = new class implements IDomClassList { // In IE11 there is only partial support for `classList` which makes us keep our // custom implementation. Otherwise use the native implementation, see: http://caniuse.com/#search=classlist const _classList: IDomClassList = browser.isIE ? _manualClassList : _nativeClassList; -export const hasClass: (node: HTMLElement, className: string) => boolean = _classList.hasClass.bind(_classList); -export const addClass: (node: HTMLElement, className: string) => void = _classList.addClass.bind(_classList); -export const addClasses: (node: HTMLElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList); -export const removeClass: (node: HTMLElement, className: string) => void = _classList.removeClass.bind(_classList); -export const removeClasses: (node: HTMLElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList); -export const toggleClass: (node: HTMLElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList); +export const hasClass: (node: HTMLElement | SVGElement, className: string) => boolean = _classList.hasClass.bind(_classList); +export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList); +export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList); +export const removeClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.removeClass.bind(_classList); +export const removeClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList); +export const toggleClass: (node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList); class DomListener implements IDisposable { @@ -804,8 +804,7 @@ export function createCSSRule(selector: string, cssText: string, style: HTMLStyl if (!style || !cssText) { return; } - - (style.sheet).insertRule(selector + '{' + cssText + '}', 0); + style.textContent = `${selector}{${cssText}}\n${style.textContent}`; } export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLStyleElement = getSharedStyleSheet()): void { @@ -858,6 +857,8 @@ export const EventType = { ERROR: 'error', RESIZE: 'resize', SCROLL: 'scroll', + FULLSCREEN_CHANGE: 'fullscreenchange', + WK_FULLSCREEN_CHANGE: 'webkitfullscreenchange', // Form SELECT: 'select', CHANGE: 'change', @@ -987,14 +988,28 @@ export function prepend(parent: HTMLElement, child: T): T { const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((.([\w\-]+))*)/; -export function $(description: string, attrs?: { [key: string]: any; }, ...children: Array): T { +export enum Namespace { + HTML = 'http://www.w3.org/1999/xhtml', + SVG = 'http://www.w3.org/2000/svg' +} + +function _$(namespace: Namespace, description: string, attrs?: { [key: string]: any; }, ...children: Array): T { let match = SELECTOR_REGEX.exec(description); if (!match) { throw new Error('Bad use of emmet'); } - let result = document.createElement(match[1] || 'div'); + attrs = { ...(attrs || {}) }; + + let tagName = match[1] || 'div'; + let result: T; + + if (namespace !== Namespace.HTML) { + result = document.createElementNS(namespace as string, tagName) as T; + } else { + result = document.createElement(tagName) as unknown as T; + } if (match[3]) { result.id = match[3]; @@ -1003,7 +1018,6 @@ export function $(description: string, attrs?: { [key: st result.className = match[4].replace(/\./g, ' ').trim(); } - attrs = attrs || {}; Object.keys(attrs).forEach(name => { const value = attrs![name]; if (/^on\w+$/.test(name)) { @@ -1030,6 +1044,14 @@ export function $(description: string, attrs?: { [key: st return result as T; } +export function $(description: string, attrs?: { [key: string]: any; }, ...children: Array): T { + return _$(Namespace.HTML, description, attrs, ...children); +} + +$.SVG = function (description: string, attrs?: { [key: string]: any; }, ...children: Array): T { + return _$(Namespace.SVG, description, attrs, ...children); +}; + export function join(nodes: Node[], separator: Node | string): Node[] { const result: Node[] = []; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index e76fd3a6bbc..ffef9180cc6 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -259,9 +259,6 @@ export class ActionViewItem extends BaseActionViewItem { this.label.setAttribute('role', 'menuitem'); } else { this.label.setAttribute('role', 'button'); - - // TODO @misolori remove before shipping stable - this.label.setAttribute('data-title', this._action.id); } } diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index ac1557375b5..1f9217cc9dd 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -108,6 +108,10 @@ export class BaseDropdown extends ActionRunner { this.visible = false; } + isVisible(): boolean { + return this.visible; + } + protected onEvent(e: Event, activeElement: HTMLElement): void { this.hide(); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index b3cc40b3eee..4c005afeea7 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -857,6 +857,7 @@ export interface IListStyles { listFilterWidgetOutline?: Color; listFilterWidgetNoMatchesOutline?: Color; listMatchesShadow?: Color; + treeIndentGuidesStroke?: Color; } const defaultStyles: IListStyles = { @@ -867,7 +868,8 @@ const defaultStyles: IListStyles = { listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'), listInactiveSelectionBackground: Color.fromHex('#3F3F46'), listHoverBackground: Color.fromHex('#2A2D2E'), - listDropBackground: Color.fromHex('#383B3D') + listDropBackground: Color.fromHex('#383B3D'), + treeIndentGuidesStroke: Color.fromHex('#a9a9a9') }; const DefaultOptions = { @@ -1112,6 +1114,7 @@ export class List implements ISpliceable, IDisposable { return Event.map(this._onPin.event, indexes => this.toListEvent({ indexes })); } + get domId(): string { return this.view.domId; } get onDidScroll(): Event { return this.view.onDidScroll; } get onMouseClick(): Event> { return this.view.onMouseClick; } get onMouseDblClick(): Event> { return this.view.onMouseDblClick; } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 57268648e33..7913bb42fd5 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -18,7 +18,7 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { Event, Emitter } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { isLinux } from 'vs/base/common/platform'; +import { isLinux, isMacintosh } from 'vs/base/common/platform'; function createMenuMnemonicRegExp() { try { @@ -91,7 +91,7 @@ export class Menu extends ActionBar { context: options.context, actionRunner: options.actionRunner, ariaLabel: options.ariaLabel, - triggerKeys: { keys: [KeyCode.Enter], keyDown: true } + triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh ? [KeyCode.Space] : [])], keyDown: true } }); this.menuElement = menuElement; @@ -109,7 +109,7 @@ export class Menu extends ActionBar { // Stop tab navigation of menus if (event.equals(KeyCode.Tab)) { - EventHelper.stop(e, true); + e.preventDefault(); } }); diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index e74092b1148..1ffcb47b9a1 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -14,10 +14,12 @@ import { cleanMnemonic, IMenuOptions, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MN import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; -import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { KeyCode, ResolvedKeybinding, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; import { asArray } from 'vs/base/common/arrays'; +import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode'; +import { isMacintosh } from 'vs/base/common/platform'; const $ = DOM.$; @@ -115,9 +117,9 @@ export class MenuBar extends Disposable { let eventHandled = true; const key = !!e.key ? e.key.toLocaleLowerCase() : ''; - if (event.equals(KeyCode.LeftArrow)) { + if (event.equals(KeyCode.LeftArrow) || (isMacintosh && event.equals(KeyCode.Tab | KeyMod.Shift))) { this.focusPrevious(); - } else if (event.equals(KeyCode.RightArrow)) { + } else if (event.equals(KeyCode.RightArrow) || (isMacintosh && event.equals(KeyCode.Tab))) { this.focusNext(); } else if (event.equals(KeyCode.Escape) && this.isFocused && !this.isOpen) { this.setUnfocusedState(); @@ -128,6 +130,11 @@ export class MenuBar extends Disposable { eventHandled = false; } + // Never allow default tab behavior + if (event.equals(KeyCode.Tab | KeyMod.Shift) || event.equals(KeyCode.Tab)) { + event.preventDefault(); + } + if (eventHandled) { event.preventDefault(); event.stopPropagation(); @@ -777,6 +784,13 @@ export class MenuBar extends Disposable { return; } + // Prevent alt-key default if the menu is not hidden and we use alt to focus + if (modifierKeyStatus.event && !this.options.disableAltFocus) { + if (ScanCodeUtils.toEnum(modifierKeyStatus.event.code) === ScanCode.AltLeft) { + modifierKeyStatus.event.preventDefault(); + } + } + // Alt key pressed while menu is focused. This should return focus away from the menubar if (this.isFocused && modifierKeyStatus.lastKeyPressed === 'alt' && modifierKeyStatus.altKey) { this.setUnfocusedState(); @@ -892,6 +906,7 @@ interface IModifierKeyStatus { ctrlKey: boolean; lastKeyPressed?: ModifierKey; lastKeyReleased?: ModifierKey; + event?: KeyboardEvent; } @@ -930,6 +945,7 @@ class ModifierKeyEmitter extends Emitter { this._keyStatus.shiftKey = e.shiftKey; if (this._keyStatus.lastKeyPressed) { + this._keyStatus.event = e; this.fire(this._keyStatus); } })); @@ -954,6 +970,7 @@ class ModifierKeyEmitter extends Emitter { this._keyStatus.shiftKey = e.shiftKey; if (this._keyStatus.lastKeyReleased) { + this._keyStatus.event = e; this.fire(this._keyStatus); } })); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 0bda3de3426..dad7d26545a 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/tree'; -import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IListOptions, List, IListStyles, mightProducePrintableCharacter, MouseController } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list'; -import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass } from 'vs/base/browser/dom'; +import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom'; import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -25,6 +25,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import { values } from 'vs/base/common/map'; import { clamp } from 'vs/base/common/numbers'; import { ScrollEvent } from 'vs/base/common/scrollable'; +import { SetMap } from 'vs/base/common/collections'; function asTreeDragAndDropData(data: IDragAndDropData): IDragAndDropData { if (data instanceof ElementsDragAndDropData) { @@ -188,12 +189,48 @@ export class ComposedTreeDelegate implements IListV interface ITreeListTemplateData { readonly container: HTMLElement; + readonly indent: HTMLElement; readonly twistie: HTMLElement; + indentGuidesDisposable: IDisposable; readonly templateData: T; } +export enum RenderIndentGuides { + None = 'none', + OnHover = 'onHover', + Always = 'always' +} + interface ITreeRendererOptions { readonly indent?: number; + readonly renderIndentGuides?: RenderIndentGuides; +} + +interface IRenderData { + templateData: ITreeListTemplateData; + height: number; +} + +interface Collection { + readonly elements: T[]; + readonly onDidChange: Event; +} + +class EventCollection implements Collection { + + private disposables = new DisposableStore(); + + get elements(): T[] { + return this._elements; + } + + constructor(readonly onDidChange: Event, private _elements: T[] = []) { + onDidChange(e => this._elements = e, null, this.disposables); + } + + dispose() { + this.disposables.dispose(); + } } class TreeRenderer implements IListRenderer, ITreeListTemplateData> { @@ -202,13 +239,20 @@ class TreeRenderer implements IListRenderer>(); - private renderedNodes = new Map, ITreeListTemplateData>(); + private renderedNodes = new Map, IRenderData>(); private indent: number = TreeRenderer.DefaultIndent; + + private _renderIndentGuides: RenderIndentGuides = RenderIndentGuides.None; + private renderedIndentGuides = new SetMap, HTMLDivElement>(); + private activeParentNodes = new Set>(); + private indentGuidesDisposable: IDisposable = Disposable.None; + private disposables: IDisposable[] = []; constructor( private renderer: ITreeRenderer, onDidChangeCollapseState: Event>, + private activeNodes: Collection>, options: ITreeRendererOptions = {} ) { this.templateId = renderer.templateId; @@ -226,34 +270,57 @@ class TreeRenderer implements IListRenderer { - templateData.twistie.style.marginLeft = `${node.depth * this.indent}px`; - }); + if (typeof options.renderIndentGuides !== 'undefined') { + const renderIndentGuides = options.renderIndentGuides; + + if (renderIndentGuides !== this._renderIndentGuides) { + this._renderIndentGuides = renderIndentGuides; + + if (renderIndentGuides) { + const disposables = new DisposableStore(); + this.activeNodes.onDidChange(this._onDidChangeActiveNodes, this, disposables); + this.indentGuidesDisposable = disposables; + + this._onDidChangeActiveNodes(this.activeNodes.elements); + } else { + this.indentGuidesDisposable.dispose(); + } + } + } } renderTemplate(container: HTMLElement): ITreeListTemplateData { const el = append(container, $('.monaco-tl-row')); + const indent = append(el, $('.monaco-tl-indent')); const twistie = append(el, $('.monaco-tl-twistie')); const contents = append(el, $('.monaco-tl-contents')); const templateData = this.renderer.renderTemplate(contents); - return { container, twistie, templateData }; + return { container, indent, twistie, indentGuidesDisposable: Disposable.None, templateData }; } renderElement(node: ITreeNode, index: number, templateData: ITreeListTemplateData, height: number | undefined): void { if (typeof height === 'number') { - this.renderedNodes.set(node, templateData); + this.renderedNodes.set(node, { templateData, height }); this.renderedElements.set(node.element, node); } const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent; templateData.twistie.style.marginLeft = `${indent}px`; - this.update(node, templateData); + templateData.indent.style.width = `${indent + this.indent - 16}px`; + + this.renderTwistie(node, templateData); + + if (typeof height === 'number') { + this.renderIndentGuides(node, templateData); + } this.renderer.renderElement(node, index, templateData.templateData, height); } disposeElement(node: ITreeNode, index: number, templateData: ITreeListTemplateData, height: number | undefined): void { + templateData.indentGuidesDisposable.dispose(); + if (this.renderer.disposeElement) { this.renderer.disposeElement(node, index, templateData.templateData, height); } @@ -279,16 +346,17 @@ class TreeRenderer implements IListRenderer): void { - const templateData = this.renderedNodes.get(node); + const data = this.renderedNodes.get(node); - if (!templateData) { + if (!data) { return; } - this.update(node, templateData); + this.renderTwistie(node, data.templateData); + this.renderIndentGuides(node, data.templateData); } - private update(node: ITreeNode, templateData: ITreeListTemplateData) { + private renderTwistie(node: ITreeNode, templateData: ITreeListTemplateData) { if (this.renderer.renderTwistie) { this.renderer.renderTwistie(node.element, templateData.twistie); } @@ -303,9 +371,72 @@ class TreeRenderer implements IListRenderer, templateData: ITreeListTemplateData): void { + clearNode(templateData.indent); + templateData.indentGuidesDisposable.dispose(); + + if (this._renderIndentGuides === RenderIndentGuides.None) { + return; + } + + const disposableStore = new DisposableStore(); + let node = target; + + while (node.parent && node.parent.parent) { + const parent = node.parent; + const guide = $('.indent-guide', { style: `width: ${this.indent}px` }); + + if (this.activeParentNodes.has(parent)) { + addClass(guide, 'active'); + } + + if (templateData.indent.childElementCount === 0) { + templateData.indent.appendChild(guide); + } else { + templateData.indent.insertBefore(guide, templateData.indent.firstElementChild); + } + + this.renderedIndentGuides.add(parent, guide); + disposableStore.add(toDisposable(() => this.renderedIndentGuides.delete(parent, guide))); + + node = parent; + } + + templateData.indentGuidesDisposable = disposableStore; + } + + private _onDidChangeActiveNodes(nodes: ITreeNode[]): void { + if (this._renderIndentGuides === RenderIndentGuides.None) { + return; + } + + const set = new Set>(); + + nodes.forEach(node => { + if (node.parent) { + set.add(node.parent); + } + }); + + this.activeParentNodes.forEach(node => { + if (!set.has(node)) { + this.renderedIndentGuides.forEach(node, line => removeClass(line, 'active')); + } + }); + + set.forEach(node => { + if (!this.activeParentNodes.has(node)) { + this.renderedIndentGuides.forEach(node, line => addClass(line, 'active')); + } + }); + + this.activeParentNodes = set; + } + dispose(): void { this.renderedNodes.clear(); this.renderedElements.clear(); + this.indentGuidesDisposable.dispose(); this.disposables = dispose(this.disposables); } } @@ -820,6 +951,10 @@ class Trait { return [...this.elements]; } + getNodes(): readonly ITreeNode[] { + return this.nodes; + } + has(node: ITreeNode): boolean { return this.nodeSet.has(node); } @@ -1008,6 +1143,7 @@ export abstract class AbstractTree implements IDisposable private eventBufferer = new EventBufferer(); private typeFilterController?: TypeFilterController; private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; + private styleElement: HTMLStyleElement; protected disposables: IDisposable[] = []; get onDidScroll(): Event { return this.view.onDidScroll; } @@ -1036,7 +1172,6 @@ export abstract class AbstractTree implements IDisposable get filterOnType(): boolean { return !!this._options.filterOnType; } get onDidChangeTypeFilterPattern(): Event { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; } - // Options TODO@joao expose options only, not Optional<> get openOnSingleClick(): boolean { return typeof this._options.openOnSingleClick === 'undefined' ? true : this._options.openOnSingleClick; } get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; } @@ -1054,7 +1189,11 @@ export abstract class AbstractTree implements IDisposable const treeDelegate = new ComposedTreeDelegate>(delegate); const onDidChangeCollapseStateRelay = new Relay>(); - this.renderers = renderers.map(r => new TreeRenderer(r, onDidChangeCollapseStateRelay.event, _options)); + const onDidChangeActiveNodes = new Relay[]>(); + const activeNodes = new EventCollection(onDidChangeActiveNodes.event); + this.disposables.push(activeNodes); + + this.renderers = renderers.map(r => new TreeRenderer(r, onDidChangeCollapseStateRelay.event, activeNodes, _options)); this.disposables.push(...this.renderers); let filter: TypeFilter | undefined; @@ -1077,6 +1216,8 @@ export abstract class AbstractTree implements IDisposable this.selection.onDidModelSplice(e); }, null, this.disposables); + onDidChangeActiveNodes.input = Event.map(Event.any(this.focus.onDidChange, this.selection.onDidChange, this.model.onDidSplice), () => [...this.focus.getNodes(), ...this.selection.getNodes()]); + if (_options.keyboardSupport !== false) { const onKeyDown = Event.chain(this.view.onKeyDown) .filter(e => !isInputElement(e.target as HTMLElement)) @@ -1092,6 +1233,9 @@ export abstract class AbstractTree implements IDisposable this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node); this.disposables.push(this.typeFilterController!); } + + this.styleElement = createStyleSheet(this.view.getHTMLElement()); + toggleClass(this.getHTMLElement(), 'always', this._options.renderIndentGuides === RenderIndentGuides.Always); } updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { @@ -1111,6 +1255,8 @@ export abstract class AbstractTree implements IDisposable } this._onDidUpdateOptions.fire(this._options); + + toggleClass(this.getHTMLElement(), 'always', this._options.renderIndentGuides === RenderIndentGuides.Always); } get options(): IAbstractTreeOptions { @@ -1192,6 +1338,19 @@ export abstract class AbstractTree implements IDisposable } style(styles: IListStyles): void { + const suffix = `.${this.view.domId}`; + const content: string[] = []; + + if (styles.treeIndentGuidesStroke) { + content.push(`.monaco-list${suffix}:hover .monaco-tl-indent > .indent-guide, .monaco-list${suffix}.always .monaco-tl-indent > .indent-guide { border-color: ${styles.treeIndentGuidesStroke.transparent(0.4)}; }`); + content.push(`.monaco-list${suffix} .monaco-tl-indent > .indent-guide.active { border-color: ${styles.treeIndentGuidesStroke}; }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this.styleElement.innerHTML) { + this.styleElement.innerHTML = newStyles; + } + this.view.style(styles); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 70beeb2145d..6208e4e8cce 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -225,6 +225,7 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt } }, keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && { + ...options.keyboardNavigationLabelProvider, getKeyboardNavigationLabel(e) { return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element as T); } diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index d2342d7e85b..bb93ca31fad 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -18,6 +18,7 @@ export interface IDataTreeViewState { readonly focus: string[]; readonly selection: string[]; readonly expanded: string[]; + readonly scrollTop: number; } export class DataTree extends AbstractTree { @@ -80,6 +81,10 @@ export class DataTree extends AbstractTree extends AbstractTree .indent-guide { + display: inline-block; + box-sizing: border-box; + height: 100%; + border-left: 1px solid transparent; +} + +.monaco-tl-indent > .indent-guide { + transition: border-color 0.1s linear; } .monaco-tl-twistie, @@ -66,4 +90,4 @@ .hc-black .monaco-tl-twistie.loading { background-image: url("loading-hc.svg"); -} \ No newline at end of file +} diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index cf34296e74c..7b4e9cc8d66 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -78,7 +78,10 @@ export class VSBuffer { } slice(start?: number, end?: number): VSBuffer { - return new VSBuffer(this.buffer.slice(start, end)); + // IMPORTANT: use subarray instead of slice because TypedArray#slice + // creates shallow copy and NodeBuffer#slice doesn't. The use of subarray + // ensures the same, performant, behaviour. + return new VSBuffer(this.buffer.subarray(start!/*bad lib.d.ts*/, end)); } set(array: VSBuffer, offset?: number): void { @@ -437,4 +440,4 @@ class VSBufferWriteableStreamImpl implements VSBufferWriteableStream { this.listeners.end.length = 0; } } -} \ No newline at end of file +} diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index f5c5cf5408b..d0c3e84ece3 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -96,4 +96,44 @@ export function fromMap(original: Map): IStringDictionary { }); } return result; +} + +export class SetMap { + + private map = new Map>(); + + add(key: K, value: V): void { + let values = this.map.get(key); + + if (!values) { + values = new Set(); + this.map.set(key, values); + } + + values.add(value); + } + + delete(key: K, value: V): void { + const values = this.map.get(key); + + if (!values) { + return; + } + + values.delete(value); + + if (values.size === 0) { + this.map.delete(key); + } + } + + forEach(key: K, fn: (value: V) => void): void { + const values = this.map.get(key); + + if (!values) { + return; + } + + values.forEach(fn); + } } \ No newline at end of file diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index adf5859c794..d6dbb1784f1 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -7,28 +7,26 @@ import * as strings from 'vs/base/common/strings'; import { sep } from 'vs/base/common/path'; import { IdleValue } from 'vs/base/common/async'; -let intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }>; - -export function setFileNameComparer(collator: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }>): void { - intlFileNameCollator = collator; -} +const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => { + const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); + return { + collator: collator, + collatorIsNumeric: collator.resolvedOptions().numeric + }; +}); export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number { - if (intlFileNameCollator) { - const a = one || ''; - const b = other || ''; - const result = intlFileNameCollator.getValue().collator.compare(a, b); + const a = one || ''; + const b = other || ''; + const result = intlFileNameCollator.getValue().collator.compare(a, b); - // Using the numeric option in the collator will - // make compare(`foo1`, `foo01`) === 0. We must disambiguate. - if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && a !== b) { - return a < b ? -1 : 1; - } - - return result; + // Using the numeric option in the collator will + // make compare(`foo1`, `foo01`) === 0. We must disambiguate. + if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && a !== b) { + return a < b ? -1 : 1; } - return noIntlCompareFileNames(one, other, caseSensitive); + return result; } const FileNameMatch = /^(.*?)(\.([^.]*))?$/; @@ -54,46 +52,27 @@ export function noIntlCompareFileNames(one: string | null, other: string | null, } export function compareFileExtensions(one: string | null, other: string | null): number { - if (intlFileNameCollator) { - const [oneName, oneExtension] = extractNameAndExtension(one); - const [otherName, otherExtension] = extractNameAndExtension(other); + const [oneName, oneExtension] = extractNameAndExtension(one); + const [otherName, otherExtension] = extractNameAndExtension(other); - let result = intlFileNameCollator.getValue().collator.compare(oneExtension, otherExtension); + let result = intlFileNameCollator.getValue().collator.compare(oneExtension, otherExtension); - if (result === 0) { - // Using the numeric option in the collator will - // make compare(`foo1`, `foo01`) === 0. We must disambiguate. - if (intlFileNameCollator.getValue().collatorIsNumeric && oneExtension !== otherExtension) { - return oneExtension < otherExtension ? -1 : 1; - } - - // Extensions are equal, compare filenames - result = intlFileNameCollator.getValue().collator.compare(oneName, otherName); - - if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) { - return oneName < otherName ? -1 : 1; - } + if (result === 0) { + // Using the numeric option in the collator will + // make compare(`foo1`, `foo01`) === 0. We must disambiguate. + if (intlFileNameCollator.getValue().collatorIsNumeric && oneExtension !== otherExtension) { + return oneExtension < otherExtension ? -1 : 1; } - return result; + // Extensions are equal, compare filenames + result = intlFileNameCollator.getValue().collator.compare(oneName, otherName); + + if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) { + return oneName < otherName ? -1 : 1; + } } - return noIntlCompareFileExtensions(one, other); -} - -function noIntlCompareFileExtensions(one: string | null, other: string | null): number { - const [oneName, oneExtension] = extractNameAndExtension(one && one.toLowerCase()); - const [otherName, otherExtension] = extractNameAndExtension(other && other.toLowerCase()); - - if (oneExtension !== otherExtension) { - return oneExtension < otherExtension ? -1 : 1; - } - - if (oneName === otherName) { - return 0; - } - - return oneName < otherName ? -1 : 1; + return result; } function extractNameAndExtension(str?: string | null): [string, string] { diff --git a/src/vs/base/test/browser/comparers.test.ts b/src/vs/base/test/browser/comparers.test.ts index df74e2bbfa2..369f160803b 100644 --- a/src/vs/base/test/browser/comparers.test.ts +++ b/src/vs/base/test/browser/comparers.test.ts @@ -3,23 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareFileNames, compareFileExtensions, setFileNameComparer } from 'vs/base/common/comparers'; +import { compareFileNames, compareFileExtensions } from 'vs/base/common/comparers'; import * as assert from 'assert'; -import { IdleValue } from 'vs/base/common/async'; suite('Comparers', () => { test('compareFileNames', () => { - // Setup Intl - setFileNameComparer(new IdleValue(() => { - const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); - return { - collator: collator, - collatorIsNumeric: collator.resolvedOptions().numeric - }; - })); - assert(compareFileNames(null, null) === 0, 'null should be equal'); assert(compareFileNames(null, 'abc') < 0, 'null should be come before real values'); assert(compareFileNames('', '') === 0, 'empty should be equal'); @@ -35,15 +25,6 @@ suite('Comparers', () => { test('compareFileExtensions', () => { - // Setup Intl - setFileNameComparer(new IdleValue(() => { - const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); - return { - collator: collator, - collatorIsNumeric: collator.resolvedOptions().numeric - }; - })); - assert(compareFileExtensions(null, null) === 0, 'null should be equal'); assert(compareFileExtensions(null, '.abc') < 0, 'null should come before real files'); assert(compareFileExtensions(null, 'abc') < 0, 'null should come before real files without extension'); @@ -66,4 +47,4 @@ suite('Comparers', () => { assert(compareFileExtensions('file2.ext2', 'file1.ext10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order'); assert(compareFileExtensions('file.ext01', 'file.ext1') < 0, 'extensions with equal numbers should be in alphabetical order'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/buffer.test.ts b/src/vs/base/test/node/buffer.test.ts similarity index 90% rename from src/vs/base/test/common/buffer.test.ts rename to src/vs/base/test/node/buffer.test.ts index c5a3cd676ee..65f84a66ddf 100644 --- a/src/vs/base/test/common/buffer.test.ts +++ b/src/vs/base/test/node/buffer.test.ts @@ -364,4 +364,42 @@ suite('Buffer', () => { assert.equal(ended, false); assert.equal(errors.length, 0); }); + + test('Performance issue with VSBuffer#slice #76076', function () { + // Buffer#slice creates a view + { + const buff = Buffer.from([10, 20, 30, 40]); + const b2 = buff.slice(1, 3); + assert.equal(buff[1], 20); + assert.equal(b2[0], 20); + + buff[1] = 17; // modify buff AND b2 + assert.equal(buff[1], 17); + assert.equal(b2[0], 17); + } + + // TypedArray#slice creates a copy + { + const unit = new Uint8Array([10, 20, 30, 40]); + const u2 = unit.slice(1, 3); + assert.equal(unit[1], 20); + assert.equal(u2[0], 20); + + unit[1] = 17; // modify unit, NOT b2 + assert.equal(unit[1], 17); + assert.equal(u2[0], 20); + } + + // TypedArray#subarray creates a view + { + const unit = new Uint8Array([10, 20, 30, 40]); + const u2 = unit.subarray(1, 3); + assert.equal(unit[1], 20); + assert.equal(u2[0], 20); + + unit[1] = 17; // modify unit AND b2 + assert.equal(unit[1], 17); + assert.equal(u2[0], 17); + } + }); }); diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 316826eea67..daf45dab6e4 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -4,27 +4,32 @@ + + + + + + + + + + + + + + + + - - - \ No newline at end of file diff --git a/src/vs/code/browser/workbench/workbench.js b/src/vs/code/browser/workbench/workbench.js index 8809f830a5a..34f321f90df 100644 --- a/src/vs/code/browser/workbench/workbench.js +++ b/src/vs/code/browser/workbench/workbench.js @@ -3,12 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -//@ts-check 'use strict'; (function () { - // @ts-ignore require.config({ baseUrl: `${window.location.origin}/out`, paths: { @@ -20,10 +18,9 @@ } }); - // @ts-ignore - require(['vs/workbench/workbench.web.api'], function () { - // @ts-ignore - // eslint-disable-next-line no-undef - monaco.workbench.create(document.body, self.WINDOW_CONFIGURATION); + require(['vs/workbench/workbench.web.api'], function (api) { + const options = JSON.parse(document.getElementById('vscode-workbench-web-configuration').getAttribute('data-settings')); + + api.create(document.body, options); }); })(); \ No newline at end of file diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index d753746fe85..fb2369cb5c1 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -33,7 +33,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel'; import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc'; +import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index e3632ba5b91..c1cda361423 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -31,7 +31,7 @@ import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { ipcRenderer } from 'electron'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc'; +import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc'; @@ -48,6 +48,9 @@ import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/ import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; +import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { DiagnosticsChannel } from 'vs/platform/diagnostics/node/diagnosticsIpc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -121,6 +124,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const instantiationService = new InstantiationService(services); + let telemetryService: ITelemetryService; instantiationService.invokeFunction(accessor => { const services = new ServiceCollection(); const environmentService = accessor.get(IEnvironmentService); @@ -141,8 +145,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat piiPaths: [appRoot, extensionsPath] }; - services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); + telemetryService = new TelemetryService(config, configurationService); + services.set(ITelemetryService, telemetryService); } else { + telemetryService = NullTelemetryService; services.set(ITelemetryService, NullTelemetryService); } server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appInsightsAppender)); @@ -150,6 +156,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); + services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); const instantiationService2 = instantiationService.createChild(services); @@ -163,6 +170,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const localizationsChannel = new LocalizationsChannel(localizationsService); server.registerChannel('localizations', localizationsChannel); + const diagnosticsService = accessor.get(IDiagnosticsService); + const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService); + server.registerChannel('diagnostics', diagnosticsChannel); + // clean up deprecated extensions (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(); // update localizations cache diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 97aaf7a658a..e9afa5d4ae6 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -50,7 +50,7 @@ import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateServ import { IIssueService } from 'vs/platform/issue/common/issue'; import { IssueChannel } from 'vs/platform/issue/node/issueIpc'; import { IssueService } from 'vs/platform/issue/electron-main/issueService'; -import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; +import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; @@ -81,6 +81,8 @@ import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFacto import { VSBuffer } from 'vs/base/common/buffer'; import { statSync } from 'fs'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc'; export class CodeApplication extends Disposable { @@ -407,6 +409,10 @@ export class CodeApplication extends Disposable { services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv])); services.set(IWindowsService, new SyncDescriptor(WindowsService, [sharedProcess])); services.set(ILaunchService, new SyncDescriptor(LaunchService)); + + const diagnosticsChannel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('diagnostics'))); + services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel])); + services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv])); services.set(IMenubarService, new SyncDescriptor(MenubarService)); @@ -523,7 +529,7 @@ export class CodeApplication extends Disposable { this.lifecycleService.phase = LifecycleMainPhase.Ready; // Propagate to clients - const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this + const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // Create a URL handler which forwards to the last active window const activeWindowManager = new ActiveWindowManager(windowsService); diff --git a/src/vs/code/electron-main/logUploader.ts b/src/vs/code/electron-main/logUploader.ts deleted file mode 100644 index 4e601fe8a65..00000000000 --- a/src/vs/code/electron-main/logUploader.ts +++ /dev/null @@ -1,153 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as os from 'os'; -import * as cp from 'child_process'; -import * as fs from 'fs'; - -import * as path from 'vs/base/common/path'; -import { localize } from 'vs/nls'; -import product from 'vs/platform/product/node/product'; -import { IRequestService } from 'vs/platform/request/node/request'; -import { IRequestContext } from 'vs/base/node/request'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ILaunchService } from 'vs/platform/launch/electron-main/launchService'; - -interface PostResult { - readonly blob_id: string; -} - -class Endpoint { - private constructor( - readonly url: string - ) { } - - static getFromProduct(): Endpoint | undefined { - const logUploaderUrl = product.logUploaderUrl; - return logUploaderUrl ? new Endpoint(logUploaderUrl) : undefined; - } -} - -export async function uploadLogs( - launchService: ILaunchService, - requestService: IRequestService, - environmentService: IEnvironmentService -): Promise { - const endpoint = Endpoint.getFromProduct(); - if (!endpoint) { - console.error(localize('invalidEndpoint', 'Invalid log uploader endpoint')); - return; - } - - const logsPath = await launchService.getLogsPath(); - - if (await promptUserToConfirmLogUpload(logsPath, environmentService)) { - console.log(localize('beginUploading', 'Uploading...')); - const outZip = await zipLogs(logsPath); - const result = await postLogs(endpoint, outZip, requestService); - console.log(localize('didUploadLogs', 'Upload successful! Log file ID: {0}', result.blob_id)); - } -} - -function promptUserToConfirmLogUpload( - logsPath: string, - environmentService: IEnvironmentService -): boolean { - const confirmKey = 'iConfirmLogsUpload'; - if ((environmentService.args['upload-logs'] || '').toLowerCase() === confirmKey.toLowerCase()) { - return true; - } else { - const message = localize('logUploadPromptHeader', 'You are about to upload your session logs to a secure Microsoft endpoint that only Microsoft\'s members of the VS Code team can access.') - + '\n\n' + localize('logUploadPromptBody', 'Session logs may contain personal information such as full paths or file contents. Please review and redact your session log files here: \'{0}\'', logsPath) - + '\n\n' + localize('logUploadPromptBodyDetails', 'By continuing you confirm that you have reviewed and redacted your session log files and that you agree to Microsoft using them to debug VS Code.') - + '\n\n' + localize('logUploadPromptAcceptInstructions', 'Please run code with \'--upload-logs={0}\' to proceed with upload', confirmKey); - console.log(message); - return false; - } -} - -async function postLogs( - endpoint: Endpoint, - outZip: string, - requestService: IRequestService -): Promise { - const dotter = setInterval(() => console.log('.'), 5000); - let result: IRequestContext; - try { - result = await requestService.request({ - url: endpoint.url, - type: 'POST', - data: Buffer.from(fs.readFileSync(outZip)).toString('base64'), - headers: { - 'Content-Type': 'application/zip' - } - }, CancellationToken.None); - } catch (e) { - clearInterval(dotter); - console.log(localize('postError', 'Error posting logs: {0}', e)); - throw e; - } - - return new Promise((resolve, reject) => { - const parts: Buffer[] = []; - result.stream.on('data', data => { - parts.push(data); - }); - - result.stream.on('end', () => { - clearInterval(dotter); - try { - const response = Buffer.concat(parts).toString('utf-8'); - if (result.res.statusCode === 200) { - resolve(JSON.parse(response)); - } else { - const errorMessage = localize('responseError', 'Error posting logs. Got {0} — {1}', result.res.statusCode, response); - console.log(errorMessage); - reject(new Error(errorMessage)); - } - } catch (e) { - console.log(localize('parseError', 'Error parsing response')); - reject(e); - } - }); - }); -} - -function zipLogs( - logsPath: string -): Promise { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-log-upload')); - const outZip = path.join(tempDir, 'logs.zip'); - return new Promise((resolve, reject) => { - doZip(logsPath, outZip, tempDir, (err, stdout, stderr) => { - if (err) { - console.error(localize('zipError', 'Error zipping logs: {0}', err.message)); - reject(err); - } else { - resolve(outZip); - } - }); - }); -} - -function doZip( - logsPath: string, - outZip: string, - tempDir: string, - callback: (error: Error, stdout: string, stderr: string) => void -) { - switch (os.platform()) { - case 'win32': - // Copy directory first to avoid file locking issues - const sub = path.join(tempDir, 'sub'); - return cp.execFile('powershell', ['-Command', - `[System.IO.Directory]::CreateDirectory("${sub}"); Copy-Item -recurse "${logsPath}" "${sub}"; Compress-Archive -Path "${sub}" -DestinationPath "${outZip}"`], - { cwd: logsPath }, - callback); - default: - return cp.execFile('zip', ['-r', outZip, '.'], { cwd: logsPath }, callback); - } -} diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index e881d6a1e2f..97578e19252 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -33,15 +33,14 @@ import { CodeApplication } from 'vs/code/electron-main/app'; import { localize } from 'vs/nls'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; -import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics/electron-main/diagnosticsService'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; -import { uploadLogs } from 'vs/code/electron-main/logUploader'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { once } from 'vs/base/common/functional'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; +import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc'; class ExpectedError extends Error { readonly isExpected = true; @@ -147,7 +146,6 @@ class CodeMain { services.set(ILifecycleService, new SyncDescriptor(LifecycleService)); services.set(IStateService, new SyncDescriptor(StateService)); services.set(IRequestService, new SyncDescriptor(RequestService)); - services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); services.set(IThemeMainService, new SyncDescriptor(ThemeMainService)); services.set(ISignService, new SyncDescriptor(SignService)); @@ -263,7 +261,7 @@ class CodeMain { // Skip this if we are running with --wait where it is expected that we wait for a while. // Also skip when gathering diagnostics (--status) which can take a longer time. let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined; - if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) { + if (!environmentService.wait && !environmentService.status) { startupWarningDialogHandle = setTimeout(() => { this.showStartupWarningDialog( localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), @@ -278,23 +276,19 @@ class CodeMain { // Process Info if (environmentService.args.status) { return instantiationService.invokeFunction(async accessor => { - const diagnostics = await accessor.get(IDiagnosticsService).getDiagnostics(launchClient); + // Create a diagnostic service connected to the existing shared process + const sharedProcessClient = await connect(environmentService.sharedIPCHandle, 'main'); + const diagnosticsChannel = sharedProcessClient.getChannel('diagnostics'); + const diagnosticsService = new DiagnosticsService(diagnosticsChannel); + const mainProcessInfo = await launchClient.getMainProcessInfo(); + const remoteDiagnostics = await launchClient.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); + const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics); console.log(diagnostics); throw new ExpectedError(); }); } - // Log uploader - if (typeof environmentService.args['upload-logs'] !== 'undefined') { - return instantiationService.invokeFunction(async accessor => { - await uploadLogs(launchClient, accessor.get(IRequestService), environmentService); - - throw new ExpectedError(); - }); - } - - // Windows: allow to set foreground if (platform.isWindows) { await this.windowsAllowSetForegroundWindow(launchClient, logService); @@ -322,13 +316,6 @@ class CodeMain { throw new ExpectedError('Terminating...'); } - // Log uploader usage info - if (typeof environmentService.args['upload-logs'] !== 'undefined') { - logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.'); - - throw new ExpectedError('Terminating...'); - } - // dock might be hidden at this case due to a retry if (platform.isMacintosh) { app.dock.show(); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index ec398df67ed..885ecc6d4c3 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -156,7 +156,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - if (isMacintosh && windowConfig && windowConfig.nativeTabs === true) { + const useNativeTabs = isMacintosh && windowConfig && windowConfig.nativeTabs === true; + if (useNativeTabs) { options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs } @@ -180,7 +181,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { // TODO@Ben (Electron 4 regression): when running on multiple displays where the target display // to open the window has a larger resolution than the primary display, the window will not size // correctly unless we set the bounds again (https://github.com/microsoft/vscode/issues/74872) - if (isMacintosh && hasMultipleDisplays) { + // + // However, when running with native tabs with multiple windows we cannot use this workaround + // because there is a potential that the new window will be added as native tab instead of being + // a window on its own. In that case calling setBounds() would cause https://github.com/microsoft/vscode/issues/75830 + if (isMacintosh && hasMultipleDisplays && (!useNativeTabs || BrowserWindow.getAllWindows().length === 1)) { if ([this.windowState.width, this.windowState.height, this.windowState.x, this.windowState.y].every(value => typeof value === 'number')) { this._win.setBounds({ width: this.windowState.width!, @@ -864,16 +869,17 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private useNativeFullScreen(): boolean { - const windowConfig = this.configurationService.getValue('window'); - if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') { - return true; // default - } + return true; + // const windowConfig = this.configurationService.getValue('window'); + // if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') { + // return true; // default + // } - if (windowConfig.nativeTabs) { - return true; // https://github.com/electron/electron/issues/16142 - } + // if (windowConfig.nativeTabs) { + // return true; // https://github.com/electron/electron/issues/16142 + // } - return windowConfig.nativeFullScreen !== false; + // return windowConfig.nativeFullScreen !== false; } isMinimized(): boolean { diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index e434b71a863..978ecdd42b8 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -25,7 +25,8 @@ function shouldSpawnCliProcess(argv: ParsedArgs): boolean { || !!argv['list-extensions'] || !!argv['install-extension'] || !!argv['uninstall-extension'] - || !!argv['locate-extension']; + || !!argv['locate-extension'] + || !!argv['telemetry']; } interface IMainCli { @@ -125,7 +126,7 @@ export async function main(argv: string[]): Promise { const processCallbacks: ((child: ChildProcess) => Promise)[] = []; - const verbose = args.verbose || args.status || typeof args['upload-logs'] !== 'undefined'; + const verbose = args.verbose || args.status; if (verbose) { env['ELECTRON_ENABLE_LOGGING'] = '1'; @@ -350,9 +351,7 @@ export async function main(argv: string[]): Promise { env }; - if (typeof args['upload-logs'] !== 'undefined') { - options['stdio'] = ['pipe', 'pipe', 'pipe']; - } else if (!verbose) { + if (!verbose) { options['stdio'] = 'ignore'; } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index dafebd1c91f..5caccaf03b9 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -41,6 +41,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { Schemas } from 'vs/base/common/network'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { buildTelemetryMessage } from 'vs/platform/environment/node/argv'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); @@ -94,6 +95,8 @@ export class Main { const arg = argv['locate-extension']; const ids: string[] = typeof arg === 'string' ? [arg] : arg; await this.locateExtension(ids); + } else if (argv['telemetry']) { + console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath)); } } diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index c175034f969..9cfeed27dd0 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -55,7 +55,7 @@ export class OpenerService implements IOpenerService { if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https) || equalsIgnoreCase(scheme, Schemas.mailto)) { // open http or default mail application - dom.windowOpenNoOpener(encodeURI(resource.toString(true))); + dom.windowOpenNoOpener(resource.toString()); return Promise.resolve(true); } else if (equalsIgnoreCase(scheme, Schemas.command)) { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 6dd6de47613..1641841c2f8 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -23,9 +23,10 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; +import { ViewLineData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel'; function getMinimapLineHeight(renderMinimap: RenderMinimap): number { if (renderMinimap === RenderMinimap.Large) { @@ -335,10 +336,7 @@ class RenderData { * Check if the current RenderData matches accurately the new desired layout and no painting is needed. */ public linesEquals(layout: MinimapLayout): boolean { - if (this.renderedLayout.startLineNumber !== layout.startLineNumber) { - return false; - } - if (this.renderedLayout.endLineNumber !== layout.endLineNumber) { + if (!this.scrollEquals(layout)) { return false; } @@ -354,6 +352,14 @@ class RenderData { return true; } + /** + * Check if the current RenderData matches the new layout's scroll position + */ + public scrollEquals(layout: MinimapLayout): boolean { + return this.renderedLayout.startLineNumber === layout.startLineNumber + && this.renderedLayout.endLineNumber === layout.endLineNumber; + } + _get(): { imageData: ImageData; rendLineNumberStart: number; lines: MinimapLine[]; } { const tmp = this._renderedLines._get(); return { @@ -435,6 +441,7 @@ export class Minimap extends ViewPart { private readonly _domNode: FastDomNode; private readonly _shadow: FastDomNode; private readonly _canvas: FastDomNode; + private readonly _decorationsCanvas: FastDomNode; private readonly _slider: FastDomNode; private readonly _sliderHorizontal: FastDomNode; private readonly _tokensColorTracker: MinimapTokensColorTracker; @@ -444,6 +451,7 @@ export class Minimap extends ViewPart { private _options: MinimapOptions; private _lastRenderData: RenderData | null; + private _renderDecorations: boolean = false; private _buffers: MinimapBuffers | null; constructor(context: ViewContext) { @@ -469,6 +477,12 @@ export class Minimap extends ViewPart { this._canvas.setLeft(0); this._domNode.appendChild(this._canvas); + this._decorationsCanvas = createFastDomNode(document.createElement('canvas')); + this._decorationsCanvas.setPosition('absolute'); + this._decorationsCanvas.setClassName('minimap-decorations-layer'); + this._decorationsCanvas.setLeft(0); + this._domNode.appendChild(this._decorationsCanvas); + this._slider = createFastDomNode(document.createElement('div')); this._slider.setPosition('absolute'); this._slider.setClassName('minimap-slider'); @@ -484,7 +498,7 @@ export class Minimap extends ViewPart { this._applyLayout(); - this._mouseDownListener = dom.addStandardDisposableListener(this._canvas.domNode, 'mousedown', (e) => { + this._mouseDownListener = dom.addStandardDisposableListener(this._domNode.domNode, 'mousedown', (e) => { e.preventDefault(); const renderMinimap = this._options.renderMinimap; @@ -513,6 +527,7 @@ export class Minimap extends ViewPart { this._sliderMouseDownListener = dom.addStandardDisposableListener(this._slider.domNode, 'mousedown', (e) => { e.preventDefault(); + e.stopPropagation(); if (e.leftButton && this._lastRenderData) { const initialMousePosition = e.posy; @@ -569,10 +584,17 @@ export class Minimap extends ViewPart { this._domNode.setWidth(this._options.minimapWidth); this._domNode.setHeight(this._options.minimapHeight); this._shadow.setHeight(this._options.minimapHeight); + this._canvas.setWidth(this._options.canvasOuterWidth); this._canvas.setHeight(this._options.canvasOuterHeight); this._canvas.domNode.width = this._options.canvasInnerWidth; this._canvas.domNode.height = this._options.canvasInnerHeight; + + this._decorationsCanvas.setWidth(this._options.canvasOuterWidth); + this._decorationsCanvas.setHeight(this._options.canvasOuterHeight); + this._decorationsCanvas.domNode.width = this._options.canvasInnerWidth; + this._decorationsCanvas.domNode.height = this._options.canvasInnerHeight; + this._slider.setWidth(this._options.minimapWidth); } @@ -629,6 +651,7 @@ export class Minimap extends ViewPart { return true; } public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + this._renderDecorations = true; return true; } public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { @@ -647,6 +670,11 @@ export class Minimap extends ViewPart { return true; } + public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + this._renderDecorations = true; + return true; + } + // --- end event handlers public prepareRender(ctx: RenderingContext): void { @@ -689,9 +717,91 @@ export class Minimap extends ViewPart { this._sliderHorizontal.setTop(0); this._sliderHorizontal.setHeight(layout.sliderHeight); + this.renderDecorations(layout); this._lastRenderData = this.renderLines(layout); } + private renderDecorations(layout: MinimapLayout) { + if (this._renderDecorations) { + this._renderDecorations = false; + const decorations = this._context.model.getDecorationsInViewport(new Range(layout.startLineNumber, 1, layout.endLineNumber, this._context.model.getLineMaxColumn(layout.endLineNumber))); + + const { renderMinimap, canvasInnerWidth, canvasInnerHeight } = this._options; + const lineHeight = getMinimapLineHeight(renderMinimap); + const characterWidth = getMinimapCharWidth(renderMinimap); + const tabSize = this._context.model.getOptions().tabSize; + const canvasContext = this._decorationsCanvas.domNode.getContext('2d')!; + + canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight); + + // If the minimap is rendered using blocks, text takes up half the line height + const lineHeightRatio = renderMinimap === RenderMinimap.LargeBlocks || renderMinimap === RenderMinimap.SmallBlocks ? 0.5 : 1; + const height = lineHeight * lineHeightRatio; + + // Loop over decorations, ignoring those that don't have the minimap property set and rendering those on the same line together + let i = 0; + for (; i < decorations.length; i++) { + if (!decorations[i].options.minimap) { + continue; + } + + let decorationsForLine = [decorations[i]]; + let currentLine = decorations[i].range.startLineNumber; + let j = i + 1; + while (j < decorations.length && decorations[j].range.startLineNumber === currentLine) { + if (decorations[j].options.minimap) { + decorationsForLine.push(decorations[j]); + } + + j += 1; + } + + i = j - 1; + this.renderDecorationsForLine(canvasContext, decorationsForLine, layout, currentLine, height, lineHeight, tabSize, characterWidth); + } + } + } + + private renderDecorationsForLine(canvasContext: CanvasRenderingContext2D, + decorations: ViewModelDecoration[], + layout: MinimapLayout, + startLineNumber: number, + height: number, + lineHeight: number, + tabSize: number, + charWidth: number) { + const y = (startLineNumber - layout.startLineNumber) * lineHeight; + + const lineData = this._context.model.getLineContent(startLineNumber); + const lineIndexToXOffset = [0]; + for (let i = 1; i < lineData.length + 1; i++) { + const charCode = lineData.charCodeAt(i - 1); + const dx = charCode === CharCode.Tab + ? tabSize * charWidth + : strings.isFullWidthCharacter(charCode) + ? 2 * charWidth + : charWidth; + + lineIndexToXOffset[i] = lineIndexToXOffset[i - 1] + dx; + } + + for (let i = 0; i < decorations.length; i++) { + const currentDecoration = decorations[i]; + const { startColumn, endColumn } = currentDecoration.range; + const x = lineIndexToXOffset[startColumn - 1]; + const width = lineIndexToXOffset[endColumn - 1] - x; + + this.renderDecoration(canvasContext, currentDecoration.options.minimap, x, y, width, height); + } + } + + private renderDecoration(canvasContext: CanvasRenderingContext2D, minimapOptions: ModelDecorationMinimapOptions, x: number, y: number, width: number, height: number) { + const decorationColor = minimapOptions.getColor(this._context.theme); + + canvasContext.fillStyle = decorationColor; + canvasContext.fillRect(x, y, width, height); + } + private renderLines(layout: MinimapLayout): RenderData { const renderMinimap = this._options.renderMinimap; const startLineNumber = layout.startLineNumber; diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index ebc34ca227f..d2fa60c5640 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -26,25 +26,45 @@ export enum OverviewRulerLane { } /** - * Options for rendering a model decoration in the overview ruler. + * Position in the minimap to render the decoration. */ -export interface IModelDecorationOverviewRulerOptions { +export enum MinimapPosition { + Inline = 1 +} + +export interface IDecorationOptions { /** - * CSS color to render in the overview ruler. + * CSS color to render. * e.g.: rgba(100, 100, 100, 0.5) or a color from the color registry */ color: string | ThemeColor | undefined; /** - * CSS color to render in the overview ruler. + * CSS color to render. * e.g.: rgba(100, 100, 100, 0.5) or a color from the color registry */ darkColor?: string | ThemeColor; +} + +/** + * Options for rendering a model decoration in the overview ruler. + */ +export interface IModelDecorationOverviewRulerOptions extends IDecorationOptions { /** * The position in the overview ruler. */ position: OverviewRulerLane; } +/** + * Options for rendering a model decoration in the overview ruler. + */ +export interface IModelDecorationMinimapOptions extends IDecorationOptions { + /** + * The position in the overview ruler. + */ + position: MinimapPosition; +} + /** * Options for a model decoration. */ @@ -89,6 +109,10 @@ export interface IModelDecorationOptions { * If set, render this decoration in the overview ruler. */ overviewRuler?: IModelDecorationOverviewRulerOptions | null; + /** + * If set, render this decoration in the minimap. + */ + minimap?: IModelDecorationMinimapOptions | null; /** * If set, the decoration will be rendered in the glyph margin with this CSS class name. */ diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 2a640ac9d01..344613d09eb 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2823,16 +2823,14 @@ function cleanClassName(className: string): string { return className.replace(/[^a-z0-9\-_]/gi, ' '); } -export class ModelDecorationOverviewRulerOptions implements model.IModelDecorationOverviewRulerOptions { +class DecorationOptions implements model.IDecorationOptions { readonly color: string | ThemeColor; readonly darkColor: string | ThemeColor; - readonly position: model.OverviewRulerLane; private _resolvedColor: string | null; - constructor(options: model.IModelDecorationOverviewRulerOptions) { + constructor(options: model.IDecorationOptions) { this.color = options.color || strings.empty; this.darkColor = options.darkColor || strings.empty; - this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center); this._resolvedColor = null; } @@ -2863,6 +2861,24 @@ export class ModelDecorationOverviewRulerOptions implements model.IModelDecorati } } +export class ModelDecorationOverviewRulerOptions extends DecorationOptions { + readonly position: model.OverviewRulerLane; + + constructor(options: model.IModelDecorationOverviewRulerOptions) { + super(options); + this.position = (typeof options.position === 'number' ? options.position : model.OverviewRulerLane.Center); + } +} + +export class ModelDecorationMinimapOptions extends DecorationOptions { + readonly position: model.MinimapPosition; + + constructor(options: model.IModelDecorationMinimapOptions) { + super(options); + this.position = options.position; + } +} + export class ModelDecorationOptions implements model.IModelDecorationOptions { public static EMPTY: ModelDecorationOptions; @@ -2884,6 +2900,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { readonly showIfCollapsed: boolean; readonly collapseOnReplaceEdit: boolean; readonly overviewRuler: ModelDecorationOverviewRulerOptions | null; + readonly minimap: ModelDecorationMinimapOptions | null; readonly glyphMarginClassName: string | null; readonly linesDecorationsClassName: string | null; readonly marginClassName: string | null; @@ -2902,6 +2919,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { this.showIfCollapsed = options.showIfCollapsed || false; this.collapseOnReplaceEdit = options.collapseOnReplaceEdit || false; this.overviewRuler = options.overviewRuler ? new ModelDecorationOverviewRulerOptions(options.overviewRuler) : null; + this.minimap = options.minimap ? new ModelDecorationMinimapOptions(options.minimap) : null; this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null; this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null; this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 113afbafc34..df80861b2a8 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -228,6 +228,13 @@ export enum OverviewRulerLane { Full = 7 } +/** + * Position in the minimap to render the decoration. + */ +export enum MinimapPosition { + Inline = 1 +} + /** * End of line character preference. */ diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index fbf4917b135..dbe2c213647 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -19,7 +19,7 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionModel, SUPPORTED_CODE_ACTIONS, CodeActionsState } from './codeActionModel'; import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger'; import { CodeActionWidget } from './codeActionWidget'; @@ -52,7 +52,7 @@ export class QuickFixController extends Disposable implements IEditorContributio editor: ICodeEditor, @IMarkerService markerService: IMarkerService, @IContextKeyService contextKeyService: IContextKeyService, - @ILocalProgressService progressService: ILocalProgressService, + @IEditorProgressService progressService: IEditorProgressService, @IContextMenuService contextMenuService: IContextMenuService, @ICommandService private readonly _commandService: ICommandService, @IKeybindingService private readonly _keybindingService: IKeybindingService, diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 8b6cc5a4863..7e71c64133d 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -14,7 +14,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { CodeActionProviderRegistry } from 'vs/editor/common/modes'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; import { CodeActionTrigger } from './codeActionTrigger'; @@ -167,7 +167,7 @@ export class CodeActionModel extends Disposable { private readonly _editor: ICodeEditor, private readonly _markerService: IMarkerService, contextKeyService: IContextKeyService, - private readonly _progressService?: ILocalProgressService + private readonly _progressService?: IEditorProgressService ) { super(); this._supportedCodeActions = SUPPORTED_CODE_ACTIONS.bindTo(contextKeyService); diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 926d6a3da2f..551ff4b4726 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -15,20 +15,18 @@ import { Range } from 'vs/editor/common/core/range'; import { SymbolKind, symbolKindToCssClass } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; -import { IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { OutlineConfigKeys } from 'vs/editor/contrib/documentSymbols/outline'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IdleValue } from 'vs/base/common/async'; export type OutlineItem = OutlineGroup | OutlineElement; export class OutlineNavigationLabelProvider implements IKeyboardNavigationLabelProvider { - constructor(@IKeybindingService private readonly _keybindingService: IKeybindingService) { } - getKeyboardNavigationLabel(element: OutlineItem): { toString(): string; } { if (element instanceof OutlineGroup) { return element.provider.displayName || element.id; @@ -36,10 +34,6 @@ export class OutlineNavigationLabelProvider implements IKeyboardNavigationLabelP return element.symbol.name; } } - - mightProducePrintableCharacter(event: IKeyboardEvent): boolean { - return this._keybindingService.mightProducePrintableCharacter(event); - } } @@ -215,6 +209,8 @@ export const enum OutlineSortOrder { export class OutlineItemComparator implements ITreeSorter { + private readonly _collator = new IdleValue(() => new Intl.Collator(undefined, { numeric: true })); + constructor( public type: OutlineSortOrder = OutlineSortOrder.ByPosition ) { } @@ -225,11 +221,11 @@ export class OutlineItemComparator implements ITreeSorter { } else if (a instanceof OutlineElement && b instanceof OutlineElement) { if (this.type === OutlineSortOrder.ByKind) { - return a.symbol.kind - b.symbol.kind || a.symbol.name.localeCompare(b.symbol.name); + return a.symbol.kind - b.symbol.kind || this._collator.getValue().compare(a.symbol.name, b.symbol.name); } else if (this.type === OutlineSortOrder.ByName) { - return a.symbol.name.localeCompare(b.symbol.name) || Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range); + return this._collator.getValue().compare(a.symbol.name, b.symbol.name) || Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range); } else if (this.type === OutlineSortOrder.ByPosition) { - return Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) || a.symbol.name.localeCompare(b.symbol.name); + return Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) || this._collator.getValue().compare(a.symbol.name, b.symbol.name); } } return 0; diff --git a/src/vs/editor/contrib/find/findDecorations.ts b/src/vs/editor/contrib/find/findDecorations.ts index bb115977387..49a5527fd55 100644 --- a/src/vs/editor/contrib/find/findDecorations.ts +++ b/src/vs/editor/contrib/find/findDecorations.ts @@ -7,7 +7,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { FindMatch, IModelDecorationsChangeAccessor, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { FindMatch, IModelDecorationsChangeAccessor, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness, MinimapPosition } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -269,6 +269,10 @@ export class FindDecorations implements IDisposable { overviewRuler: { color: themeColorFromId(overviewRulerFindMatchForeground), position: OverviewRulerLane.Center + }, + minimap: { + color: themeColorFromId(overviewRulerFindMatchForeground), + position: MinimapPosition.Inline } }); @@ -279,6 +283,10 @@ export class FindDecorations implements IDisposable { overviewRuler: { color: themeColorFromId(overviewRulerFindMatchForeground), position: OverviewRulerLane.Center + }, + minimap: { + color: themeColorFromId(overviewRulerFindMatchForeground), + position: MinimapPosition.Inline } }); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index bf6e607945c..baad3f0bb17 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -25,7 +25,7 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from './goToDefinition'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; @@ -58,7 +58,7 @@ export class DefinitionAction extends EditorAction { } const notificationService = accessor.get(INotificationService); const editorService = accessor.get(ICodeEditorService); - const progressService = accessor.get(ILocalProgressService); + const progressService = accessor.get(IEditorProgressService); const symbolNavService = accessor.get(ISymbolNavigationService); const model = editor.getModel(); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts index 7c81a73fb15..5c81fdeae57 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts @@ -10,7 +10,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { Range } from 'vs/editor/common/core/range'; +import { Range, IRange } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes'; import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -150,7 +150,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC return; } - const previewValue = this.getPreviewValue(textEditorModel, startLineNumber); + const previewValue = this.getPreviewValue(textEditorModel, startLineNumber, result); let wordRange: Range; if (result.originSelectionRange) { @@ -170,8 +170,8 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC }).then(undefined, onUnexpectedError); } - private getPreviewValue(textEditorModel: ITextModel, startLineNumber: number) { - let rangeToUse = this.getPreviewRangeBasedOnBrackets(textEditorModel, startLineNumber); + private getPreviewValue(textEditorModel: ITextModel, startLineNumber: number, result: LocationLink) { + let rangeToUse = result.targetSelectionRange ? result.range : this.getPreviewRangeBasedOnBrackets(textEditorModel, startLineNumber); const numberOfLinesInRange = rangeToUse.endLineNumber - rangeToUse.startLineNumber; if (numberOfLinesInRange >= GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES) { rangeToUse = this.getPreviewRangeBasedOnIndentation(textEditorModel, startLineNumber); @@ -181,7 +181,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC return previewValue; } - private stripIndentationFromPreviewRange(textEditorModel: ITextModel, startLineNumber: number, previewRange: Range) { + private stripIndentationFromPreviewRange(textEditorModel: ITextModel, startLineNumber: number, previewRange: IRange) { const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber); let minIndent = startIndent; diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 8f2bef91435..7d93dc2fb37 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -25,7 +25,6 @@ import { basename } from 'vs/base/common/resources'; import { IAction } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; -import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; class MessageWidget { @@ -286,10 +285,6 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._icon.className = SeverityIcon.className(MarkerSeverity.toSeverity(this._severity)); this.editor.revealPositionInCenter(position, ScrollType.Smooth); - - if (this.editor.getConfiguration().accessibilitySupport !== AccessibilitySupport.Disabled) { - this.focus(); - } } updateMarker(marker: IMarker): void { diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index ed3b65ef042..056e2cb5b35 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -27,26 +27,26 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic const HOVER_MESSAGE_GENERAL_META = new MarkdownString().appendText( platform.isMacintosh - ? nls.localize('links.navigate.mac', "Cmd + click to follow link") - : nls.localize('links.navigate', "Ctrl + click to follow link") + ? nls.localize('links.navigate.mac', "Follow link (cmd + click)") + : nls.localize('links.navigate', "Follow link (ctrl + click)") ); const HOVER_MESSAGE_COMMAND_META = new MarkdownString().appendText( platform.isMacintosh - ? nls.localize('links.command.mac', "Cmd + click to execute command") - : nls.localize('links.command', "Ctrl + click to execute command") + ? nls.localize('links.command.mac', "Execute command (cmd + click)") + : nls.localize('links.command', "Execute command (ctrl + click)") ); const HOVER_MESSAGE_GENERAL_ALT = new MarkdownString().appendText( platform.isMacintosh - ? nls.localize('links.navigate.al.mac', "Option + click to follow link") - : nls.localize('links.navigate.al', "Alt + click to follow link") + ? nls.localize('links.navigate.al.mac', "Follow link (option + click)") + : nls.localize('links.navigate.al', "Follow link (alt + click)") ); const HOVER_MESSAGE_COMMAND_ALT = new MarkdownString().appendText( platform.isMacintosh - ? nls.localize('links.command.al.mac', "Option + click to execute command") - : nls.localize('links.command.al', "Alt + click to execute command") + ? nls.localize('links.command.al.mac', "Execute command (option + click)") + : nls.localize('links.command.al', "Execute command (alt + click)") ); const decoration = { @@ -116,11 +116,11 @@ class LinkOccurrence { const message = new MarkdownString().appendText( platform.isMacintosh ? useMetaKey - ? nls.localize('links.custom.mac', "Cmd + click to {0}", link.tooltip) - : nls.localize('links.custom.mac.al', "Option + click to {0}", link.tooltip) + ? nls.localize('links.custom.mac', "{0} (cmd + click)", link.tooltip) + : nls.localize('links.custom.mac.al', "{0} (option + click)", link.tooltip) : useMetaKey - ? nls.localize('links.custom', "Ctrl + click to {0}", link.tooltip) - : nls.localize('links.custom.al', "Alt + click to {0}", link.tooltip) + ? nls.localize('links.custom', "{0} (ctrl + click)", link.tooltip) + : nls.localize('links.custom.al', "{0} (alt + click)", link.tooltip) ); options.hoverMessage = message; } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 28ec66cfaf7..ab06982895b 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; @@ -114,7 +114,7 @@ class RenameController extends Disposable implements IEditorContribution { private readonly editor: ICodeEditor, @INotificationService private readonly _notificationService: INotificationService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, - @ILocalProgressService private readonly _progressService: ILocalProgressService, + @IEditorProgressService private readonly _progressService: IEditorProgressService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, ) { diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index d3089cd1428..7dabc8b16a5 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -956,7 +956,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate, delay?: number): Promise { @@ -390,6 +391,10 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { public _dumpDebugInfo(): string { return ''; } + + public _dumpDebugInfoJSON(): string { + return ''; + } } function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { @@ -521,6 +526,10 @@ export class StandaloneTelemetryService implements ITelemetryService { return Promise.resolve(undefined); } + publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as any); + } + public getTelemetryInfo(): Promise { throw new Error(`Not available`); } diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index b09f41e79f1..8750db8f943 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -352,6 +352,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { ScrollbarVisibility: standaloneEnums.ScrollbarVisibility, WrappingIndent: standaloneEnums.WrappingIndent, OverviewRulerLane: standaloneEnums.OverviewRulerLane, + MinimapPosition: standaloneEnums.MinimapPosition, EndOfLinePreference: standaloneEnums.EndOfLinePreference, DefaultEndOfLine: standaloneEnums.DefaultEndOfLine, EndOfLineSequence: standaloneEnums.EndOfLineSequence, diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index a9943538a82..996798d3d6a 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -13,7 +13,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; -import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleLocalProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleEditorProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; @@ -36,7 +36,7 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -44,11 +44,10 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { MenuService } from 'vs/platform/actions/common/menuService'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; -import { ISuggestMemoryService, SuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { ICodeLensCache, CodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; +import { getServices } from 'vs/platform/instantiation/common/extensions'; export interface IEditorOverrideServices { [index: string]: any; @@ -100,6 +99,11 @@ export module StaticServices { // Create a fresh service collection let result = new ServiceCollection(); + // make sure to add all services that use `registerSingleton` + for (const { id, descriptor } of getServices()) { + result.set(id, descriptor); + } + // Initialize the service collection with the overrides for (let serviceId in overrides) { if (overrides.hasOwnProperty(serviceId)) { @@ -150,17 +154,13 @@ export module StaticServices { export const codeEditorService = define(ICodeEditorService, (o) => new StandaloneCodeEditorServiceImpl(standaloneThemeService.get(o))); - export const localProgressService = define(ILocalProgressService, () => new SimpleLocalProgressService()); + export const editorProgressService = define(IEditorProgressService, () => new SimpleEditorProgressService()); export const storageService = define(IStorageService, () => new InMemoryStorageService()); export const logService = define(ILogService, () => new NullLogService()); export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o), logService.get(o))); - - export const suggestMemoryService = define(ISuggestMemoryService, (o) => new SuggestMemoryService(storageService.get(o), configurationService.get(o))); - - export const codeLensCacheService = define(ICodeLensCache, (o) => new CodeLensCache(storageService.get(o))); } export class DynamicStandaloneServices extends Disposable { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 34e8a703be4..3b1c5237e83 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1180,25 +1180,45 @@ declare namespace monaco.editor { } /** - * Options for rendering a model decoration in the overview ruler. + * Position in the minimap to render the decoration. */ - export interface IModelDecorationOverviewRulerOptions { + export enum MinimapPosition { + Inline = 1 + } + + export interface IDecorationOptions { /** - * CSS color to render in the overview ruler. + * CSS color to render. * e.g.: rgba(100, 100, 100, 0.5) or a color from the color registry */ color: string | ThemeColor | undefined; /** - * CSS color to render in the overview ruler. + * CSS color to render. * e.g.: rgba(100, 100, 100, 0.5) or a color from the color registry */ darkColor?: string | ThemeColor; + } + + /** + * Options for rendering a model decoration in the overview ruler. + */ + export interface IModelDecorationOverviewRulerOptions extends IDecorationOptions { /** * The position in the overview ruler. */ position: OverviewRulerLane; } + /** + * Options for rendering a model decoration in the overview ruler. + */ + export interface IModelDecorationMinimapOptions extends IDecorationOptions { + /** + * The position in the overview ruler. + */ + position: MinimapPosition; + } + /** * Options for a model decoration. */ @@ -1233,6 +1253,10 @@ declare namespace monaco.editor { * If set, render this decoration in the overview ruler. */ overviewRuler?: IModelDecorationOverviewRulerOptions | null; + /** + * If set, render this decoration in the minimap. + */ + minimap?: IModelDecorationMinimapOptions | null; /** * If set, the decoration will be rendered in the glyph margin with this CSS class name. */ diff --git a/src/vs/platform/diagnostics/common/diagnosticsService.ts b/src/vs/platform/diagnostics/common/diagnosticsService.ts index 21cd8952e13..496b75397c0 100644 --- a/src/vs/platform/diagnostics/common/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/common/diagnosticsService.ts @@ -5,6 +5,9 @@ import { UriComponents } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; +import { IWorkspace } from 'vs/platform/workspace/common/workspace'; export interface IMachineInfo { os: string; @@ -51,6 +54,24 @@ export interface WorkspaceStats { configFiles: WorkspaceStatItem[]; fileCount: number; maxFilesReached: boolean; + launchConfigFiles: WorkspaceStatItem[]; +} + +export interface PerformanceInfo { + processInfo?: string; + workspaceInfo?: string; +} + +export const ID = 'diagnosticsService'; +export const IDiagnosticsService = createDecorator(ID); + +export interface IDiagnosticsService { + _serviceBrand: any; + + getPerformanceInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + getSystemInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + getDiagnostics(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + reportWorkspaceStats(workspace: IWorkspace): Promise; } export function isRemoteDiagnosticError(x: any): x is IRemoteDiagnosticError { diff --git a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts deleted file mode 100644 index 1b7eb413e8c..00000000000 --- a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts +++ /dev/null @@ -1,389 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IMainProcessInfo, ILaunchService } from 'vs/platform/launch/electron-main/launchService'; -import { listProcesses } from 'vs/base/node/ps'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; -import * as osLib from 'os'; -import { virtualMachineHint } from 'vs/base/node/id'; -import { repeat, pad } from 'vs/base/common/strings'; -import { isWindows } from 'vs/base/common/platform'; -import { app } from 'electron'; -import { basename } from 'vs/base/common/path'; -import { URI } from 'vs/base/common/uri'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IMachineInfo, WorkspaceStats, SystemInfo, IRemoteDiagnosticInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; -import { collectWorkspaceStats, getMachineInfo } from 'vs/platform/diagnostics/node/diagnosticsService'; -import { ProcessItem } from 'vs/base/common/processes'; - -export const ID = 'diagnosticsService'; -export const IDiagnosticsService = createDecorator(ID); - -export interface IDiagnosticsService { - _serviceBrand: any; - - getPerformanceInfo(launchService: ILaunchService): Promise; - getSystemInfo(launchService: ILaunchService): Promise; - getDiagnostics(launchService: ILaunchService): Promise; -} - -export interface VersionInfo { - vscodeVersion: string; - os: string; -} - -export interface ProcessInfo { - cpu: number; - memory: number; - pid: number; - name: string; -} - -export interface PerformanceInfo { - processInfo?: string; - workspaceInfo?: string; -} -export class DiagnosticsService implements IDiagnosticsService { - - _serviceBrand: any; - - private formatMachineInfo(info: IMachineInfo): string { - const output: string[] = []; - output.push(`OS Version: ${info.os}`); - output.push(`CPUs: ${info.cpus}`); - output.push(`Memory (System): ${info.memory}`); - output.push(`VM: ${info.vmHint}`); - - return output.join('\n'); - } - - private formatEnvironment(info: IMainProcessInfo): string { - const MB = 1024 * 1024; - const GB = 1024 * MB; - - const output: string[] = []; - output.push(`Version: ${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`); - output.push(`OS Version: ${osLib.type()} ${osLib.arch()} ${osLib.release()}`); - const cpus = osLib.cpus(); - if (cpus && cpus.length > 0) { - output.push(`CPUs: ${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`); - } - output.push(`Memory (System): ${(osLib.totalmem() / GB).toFixed(2)}GB (${(osLib.freemem() / GB).toFixed(2)}GB free)`); - if (!isWindows) { - output.push(`Load (avg): ${osLib.loadavg().map(l => Math.round(l)).join(', ')}`); // only provided on Linux/macOS - } - output.push(`VM: ${Math.round((virtualMachineHint.value() * 100))}%`); - output.push(`Screen Reader: ${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`); - output.push(`Process Argv: ${info.mainArguments.join(' ')}`); - output.push(`GPU Status: ${this.expandGPUFeatures()}`); - - return output.join('\n'); - } - - async getPerformanceInfo(launchService: ILaunchService): Promise { - const info = await launchService.getMainProcessInfo(); - return Promise.all([listProcesses(info.mainPID), this.formatWorkspaceMetadata(info)]).then(async result => { - let [rootProcess, workspaceInfo] = result; - let processInfo = this.formatProcessList(info, rootProcess); - - try { - const remoteData = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); - remoteData.forEach(diagnostics => { - if (isRemoteDiagnosticError(diagnostics)) { - processInfo += `\n${diagnostics.errorMessage}`; - workspaceInfo += `\n${diagnostics.errorMessage}`; - } else { - processInfo += `\n\nRemote: ${diagnostics.hostName}`; - if (diagnostics.processes) { - processInfo += `\n${this.formatProcessList(info, diagnostics.processes)}`; - } - - if (diagnostics.workspaceMetadata) { - workspaceInfo += `\n| Remote: ${diagnostics.hostName}`; - for (const folder of Object.keys(diagnostics.workspaceMetadata)) { - const metadata = diagnostics.workspaceMetadata[folder]; - - let countMessage = `${metadata.fileCount} files`; - if (metadata.maxFilesReached) { - countMessage = `more than ${countMessage}`; - } - - workspaceInfo += `| Folder (${folder}): ${countMessage}`; - workspaceInfo += this.formatWorkspaceStats(metadata); - } - } - } - }); - } catch (e) { - processInfo += `\nFetching remote data failed: ${e}`; - workspaceInfo += `\nFetching remote data failed: ${e}`; - } - - return { - processInfo, - workspaceInfo - }; - }); - } - - async getSystemInfo(launchService: ILaunchService): Promise { - const info = await launchService.getMainProcessInfo(); - const { memory, vmHint, os, cpus } = getMachineInfo(); - const systemInfo: SystemInfo = { - os, - memory, - cpus, - vmHint, - processArgs: `${info.mainArguments.join(' ')}`, - gpuStatus: app.getGPUFeatureStatus(), - screenReader: `${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`, - remoteData: (await launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })).filter((x): x is IRemoteDiagnosticInfo => !(x instanceof Error)) - }; - - - if (!isWindows) { - systemInfo.load = `${osLib.loadavg().map(l => Math.round(l)).join(', ')}`; - } - - return Promise.resolve(systemInfo); - } - - async getDiagnostics(launchService: ILaunchService): Promise { - const output: string[] = []; - const info = await launchService.getMainProcessInfo(); - return listProcesses(info.mainPID).then(async rootProcess => { - - // Environment Info - output.push(''); - output.push(this.formatEnvironment(info)); - - // Process List - output.push(''); - output.push(this.formatProcessList(info, rootProcess)); - - // Workspace Stats - if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0 && !window.remoteAuthority)) { - output.push(''); - output.push('Workspace Stats: '); - output.push(await this.formatWorkspaceMetadata(info)); - } - - try { - const data = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); - data.forEach(diagnostics => { - if (isRemoteDiagnosticError(diagnostics)) { - output.push(`\n${diagnostics.errorMessage}`); - } else { - output.push('\n\n'); - output.push(`Remote: ${diagnostics.hostName}`); - output.push(this.formatMachineInfo(diagnostics.machineInfo)); - - if (diagnostics.processes) { - output.push(this.formatProcessList(info, diagnostics.processes)); - } - - if (diagnostics.workspaceMetadata) { - for (const folder of Object.keys(diagnostics.workspaceMetadata)) { - const metadata = diagnostics.workspaceMetadata[folder]; - - let countMessage = `${metadata.fileCount} files`; - if (metadata.maxFilesReached) { - countMessage = `more than ${countMessage}`; - } - - output.push(`Folder (${folder}): ${countMessage}`); - output.push(this.formatWorkspaceStats(metadata)); - } - } - } - }); - } catch (e) { - output.push('\n\n'); - output.push(`Fetching status information from remotes failed: ${e.message}`); - } - - output.push(''); - output.push(''); - - return output.join('\n'); - }); - } - - private formatWorkspaceStats(workspaceStats: WorkspaceStats): string { - const output: string[] = []; - const lineLength = 60; - let col = 0; - - const appendAndWrap = (name: string, count: number) => { - const item = ` ${name}(${count})`; - - if (col + item.length > lineLength) { - output.push(line); - line = '| '; - col = line.length; - } - else { - col += item.length; - } - line += item; - }; - - // File Types - let line = '| File types:'; - const maxShown = 10; - let max = workspaceStats.fileTypes.length > maxShown ? maxShown : workspaceStats.fileTypes.length; - for (let i = 0; i < max; i++) { - const item = workspaceStats.fileTypes[i]; - appendAndWrap(item.name, item.count); - } - output.push(line); - - // Conf Files - if (workspaceStats.configFiles.length >= 0) { - line = '| Conf files:'; - col = 0; - workspaceStats.configFiles.forEach((item) => { - appendAndWrap(item.name, item.count); - }); - output.push(line); - } - - // if (workspaceStats.launchConfigFiles.length > 0) { - // let line = '| Launch Configs:'; - // workspaceStats.launchConfigFiles.forEach(each => { - // const item = each.count > 1 ? ` ${each.name}(${each.count})` : ` ${each.name}`; - // line += item; - // }); - // output.push(line); - // } - return output.join('\n'); - } - - private expandGPUFeatures(): string { - const gpuFeatures = app.getGPUFeatureStatus(); - const longestFeatureName = Math.max(...Object.keys(gpuFeatures).map(feature => feature.length)); - // Make columns aligned by adding spaces after feature name - return Object.keys(gpuFeatures).map(feature => `${feature}: ${repeat(' ', longestFeatureName - feature.length)} ${gpuFeatures[feature]}`).join('\n '); - } - - private formatWorkspaceMetadata(info: IMainProcessInfo): Promise { - const output: string[] = []; - const workspaceStatPromises: Promise[] = []; - - info.windows.forEach(window => { - if (window.folderURIs.length === 0 || !!window.remoteAuthority) { - return; - } - - output.push(`| Window (${window.title})`); - - window.folderURIs.forEach(uriComponents => { - const folderUri = URI.revive(uriComponents); - if (folderUri.scheme === 'file') { - const folder = folderUri.fsPath; - workspaceStatPromises.push(collectWorkspaceStats(folder, ['node_modules', '.git']).then(stats => { - let countMessage = `${stats.fileCount} files`; - if (stats.maxFilesReached) { - countMessage = `more than ${countMessage}`; - } - output.push(`| Folder (${basename(folder)}): ${countMessage}`); - output.push(this.formatWorkspaceStats(stats)); - - }).catch(error => { - output.push(`| Error: Unable to collect workspace stats for folder ${folder} (${error.toString()})`); - })); - } else { - output.push(`| Folder (${folderUri.toString()}): Workspace stats not available.`); - } - }); - }); - - return Promise.all(workspaceStatPromises) - .then(_ => output.join('\n')) - .catch(e => `Unable to collect workspace stats: ${e}`); - } - - private formatProcessList(info: IMainProcessInfo, rootProcess: ProcessItem): string { - const mapPidToWindowTitle = new Map(); - info.windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title)); - - const output: string[] = []; - - output.push('CPU %\tMem MB\t PID\tProcess'); - - if (rootProcess) { - this.formatProcessItem(info.mainPID, mapPidToWindowTitle, output, rootProcess, 0); - } - - return output.join('\n'); - } - - private formatProcessItem(mainPid: number, mapPidToWindowTitle: Map, output: string[], item: ProcessItem, indent: number): void { - const isRoot = (indent === 0); - - const MB = 1024 * 1024; - - // Format name with indent - let name: string; - if (isRoot) { - name = item.pid === mainPid ? `${product.applicationName} main` : 'remote agent'; - } else { - name = `${repeat(' ', indent)} ${item.name}`; - - if (item.name === 'window') { - name = `${name} (${mapPidToWindowTitle.get(item.pid)})`; - } - } - const memory = process.platform === 'win32' ? item.mem : (osLib.totalmem() * (item.mem / 100)); - output.push(`${pad(Number(item.load.toFixed(0)), 5, ' ')}\t${pad(Number((memory / MB).toFixed(0)), 6, ' ')}\t${pad(Number((item.pid).toFixed(0)), 6, ' ')}\t${name}`); - - // Recurse into children if any - if (Array.isArray(item.children)) { - item.children.forEach(child => this.formatProcessItem(mainPid, mapPidToWindowTitle, output, child, indent + 1)); - } - } -} - -// function collectLaunchConfigs(folder: string): Promise { -// const launchConfigs = new Map(); - -// const launchConfig = join(folder, '.vscode', 'launch.json'); -// return new Promise((resolve, reject) => { -// exists(launchConfig, (doesExist) => { -// if (doesExist) { -// readFile(launchConfig, (err, contents) => { -// if (err) { -// return resolve([]); -// } - -// const errors: ParseError[] = []; -// const json = parse(contents.toString(), errors); -// if (errors.length) { -// output.push(`Unable to parse ${launchConfig}`); -// return resolve([]); -// } - -// if (json['configurations']) { -// for (const each of json['configurations']) { -// const type = each['type']; -// if (type) { -// if (launchConfigs.has(type)) { -// launchConfigs.set(type, launchConfigs.get(type)! + 1); -// } else { -// launchConfigs.set(type, 1); -// } -// } -// } -// } - -// return resolve(asSortedItems(launchConfigs)); -// }); -// } else { -// return resolve([]); -// } -// }); -// }); -// } diff --git a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts new file mode 100644 index 00000000000..28cabc55e7b --- /dev/null +++ b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IDiagnosticsService, IRemoteDiagnosticInfo, IRemoteDiagnosticError, SystemInfo, PerformanceInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { Event } from 'vs/base/common/event'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; +import { IWorkspace } from 'vs/platform/workspace/common/workspace'; + +export class DiagnosticsChannel implements IServerChannel { + + constructor(private service: IDiagnosticsService) { } + + listen(context: any, event: string): Event { + throw new Error('Invalid listen'); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'getDiagnostics': + return this.service.getDiagnostics(args[0], args[1]); + case 'getSystemInfo': + return this.service.getSystemInfo(args[0], args[1]); + case 'getPerformanceInfo': + return this.service.getPerformanceInfo(args[0], args[1]); + case 'reportWorkspaceStats': + return this.service.reportWorkspaceStats(args); + } + + throw new Error('Invalid call'); + } +} + +export class DiagnosticsService implements IDiagnosticsService { + + _serviceBrand: ServiceIdentifier; + + constructor(private channel: IChannel) { } + + public getDiagnostics(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise { + return this.channel.call('getDiagnostics', [mainProcessInfo, remoteInfo]); + } + + public getSystemInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise { + return this.channel.call('getSystemInfo', [mainProcessInfo, remoteInfo]); + } + + public getPerformanceInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise { + return this.channel.call('getPerformanceInfo', [mainProcessInfo, remoteInfo]); + } + + public reportWorkspaceStats(workspace: IWorkspace): Promise { + return this.channel.call('reportWorkspaceStats', workspace); + } +} \ No newline at end of file diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index ae056538ab9..b83d063a245 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -2,28 +2,33 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as os from 'os'; +import * as osLib from 'os'; import { virtualMachineHint } from 'vs/base/node/id'; -import { IMachineInfo, WorkspaceStats, WorkspaceStatItem } from 'vs/platform/diagnostics/common/diagnosticsService'; -import { readdir, stat } from 'fs'; -import { join } from 'vs/base/common/path'; +import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, IDiagnosticsService, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { readdir, stat, exists, readFile } from 'fs'; +import { join, basename } from 'vs/base/common/path'; +import { parse, ParseError } from 'vs/base/common/json'; +import { listProcesses } from 'vs/base/node/ps'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; +import { repeat, pad } from 'vs/base/common/strings'; +import { isWindows } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; +import { ProcessItem } from 'vs/base/common/processes'; +import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; +import { IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export function getMachineInfo(): IMachineInfo { - const MB = 1024 * 1024; - const GB = 1024 * MB; +export interface VersionInfo { + vscodeVersion: string; + os: string; +} - const machineInfo: IMachineInfo = { - os: `${os.type()} ${os.arch()} ${os.release()}`, - memory: `${(os.totalmem() / GB).toFixed(2)}GB (${(os.freemem() / GB).toFixed(2)}GB free)`, - vmHint: `${Math.round((virtualMachineHint.value() * 100))}%`, - }; - - const cpus = os.cpus(); - if (cpus && cpus.length > 0) { - machineInfo.cpus = `${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`; - } - - return machineInfo; +export interface ProcessInfo { + cpu: number; + memory: number; + pid: number; + name: string; } export function collectWorkspaceStats(folder: string, filter: string[]): Promise { @@ -145,16 +150,14 @@ export function collectWorkspaceStats(folder: string, filter: string[]): Promise walk(folder, filter, token, async (files) => { files.forEach(acceptFile); - // TODO@rachel commented out due to severe performance issues - // see https://github.com/Microsoft/vscode/issues/70563 - // const launchConfigs = await collectLaunchConfigs(folder); + const launchConfigs = await collectLaunchConfigs(folder); resolve({ configFiles: asSortedItems(configFiles), fileTypes: asSortedItems(fileTypes), fileCount: token.count, maxFilesReached: token.maxReached, - // launchConfigFiles: launchConfigs + launchConfigFiles: launchConfigs }); }); }); @@ -164,4 +167,375 @@ function asSortedItems(map: Map): WorkspaceStatItem[] { const a: WorkspaceStatItem[] = []; map.forEach((value, index) => a.push({ name: index, count: value })); return a.sort((a, b) => b.count - a.count); +} + +export function getMachineInfo(): IMachineInfo { + const MB = 1024 * 1024; + const GB = 1024 * MB; + + const machineInfo: IMachineInfo = { + os: `${osLib.type()} ${osLib.arch()} ${osLib.release()}`, + memory: `${(osLib.totalmem() / GB).toFixed(2)}GB (${(osLib.freemem() / GB).toFixed(2)}GB free)`, + vmHint: `${Math.round((virtualMachineHint.value() * 100))}%`, + }; + + const cpus = osLib.cpus(); + if (cpus && cpus.length > 0) { + machineInfo.cpus = `${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`; + } + + return machineInfo; +} + +export function collectLaunchConfigs(folder: string): Promise { + let launchConfigs = new Map(); + + let launchConfig = join(folder, '.vscode', 'launch.json'); + return new Promise((resolve, reject) => { + exists(launchConfig, (doesExist) => { + if (doesExist) { + readFile(launchConfig, (err, contents) => { + if (err) { + return resolve([]); + } + + const errors: ParseError[] = []; + const json = parse(contents.toString(), errors); + if (errors.length) { + console.log(`Unable to parse ${launchConfig}`); + return resolve([]); + } + + if (json['configurations']) { + for (const each of json['configurations']) { + const type = each['type']; + if (type) { + if (launchConfigs.has(type)) { + launchConfigs.set(type, launchConfigs.get(type)! + 1); + } else { + launchConfigs.set(type, 1); + } + } + } + } + + return resolve(asSortedItems(launchConfigs)); + }); + } else { + return resolve([]); + } + }); + }); +} + +export class DiagnosticsService implements IDiagnosticsService { + + _serviceBrand: any; + + constructor(@ITelemetryService private readonly telemetryService: ITelemetryService) { } + + private formatMachineInfo(info: IMachineInfo): string { + const output: string[] = []; + output.push(`OS Version: ${info.os}`); + output.push(`CPUs: ${info.cpus}`); + output.push(`Memory (System): ${info.memory}`); + output.push(`VM: ${info.vmHint}`); + + return output.join('\n'); + } + + private formatEnvironment(info: IMainProcessInfo): string { + const MB = 1024 * 1024; + const GB = 1024 * MB; + + const output: string[] = []; + output.push(`Version: ${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`); + output.push(`OS Version: ${osLib.type()} ${osLib.arch()} ${osLib.release()}`); + const cpus = osLib.cpus(); + if (cpus && cpus.length > 0) { + output.push(`CPUs: ${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`); + } + output.push(`Memory (System): ${(osLib.totalmem() / GB).toFixed(2)}GB (${(osLib.freemem() / GB).toFixed(2)}GB free)`); + if (!isWindows) { + output.push(`Load (avg): ${osLib.loadavg().map(l => Math.round(l)).join(', ')}`); // only provided on Linux/macOS + } + output.push(`VM: ${Math.round((virtualMachineHint.value() * 100))}%`); + output.push(`Screen Reader: ${info.screenReader ? 'yes' : 'no'}`); + output.push(`Process Argv: ${info.mainArguments.join(' ')}`); + output.push(`GPU Status: ${this.expandGPUFeatures(info.gpuFeatureStatus)}`); + + return output.join('\n'); + } + + public async getPerformanceInfo(info: IMainProcessInfo, remoteData: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise { + return Promise.all([listProcesses(info.mainPID), this.formatWorkspaceMetadata(info)]).then(async result => { + let [rootProcess, workspaceInfo] = result; + let processInfo = this.formatProcessList(info, rootProcess); + + remoteData.forEach(diagnostics => { + if (isRemoteDiagnosticError(diagnostics)) { + processInfo += `\n${diagnostics.errorMessage}`; + workspaceInfo += `\n${diagnostics.errorMessage}`; + } else { + processInfo += `\n\nRemote: ${diagnostics.hostName}`; + if (diagnostics.processes) { + processInfo += `\n${this.formatProcessList(info, diagnostics.processes)}`; + } + + if (diagnostics.workspaceMetadata) { + workspaceInfo += `\n| Remote: ${diagnostics.hostName}`; + for (const folder of Object.keys(diagnostics.workspaceMetadata)) { + const metadata = diagnostics.workspaceMetadata[folder]; + + let countMessage = `${metadata.fileCount} files`; + if (metadata.maxFilesReached) { + countMessage = `more than ${countMessage}`; + } + + workspaceInfo += `| Folder (${folder}): ${countMessage}`; + workspaceInfo += this.formatWorkspaceStats(metadata); + } + } + } + }); + + return { + processInfo, + workspaceInfo + }; + }); + } + + public async getSystemInfo(info: IMainProcessInfo, remoteData: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise { + const { memory, vmHint, os, cpus } = getMachineInfo(); + const systemInfo: SystemInfo = { + os, + memory, + cpus, + vmHint, + processArgs: `${info.mainArguments.join(' ')}`, + gpuStatus: info.gpuFeatureStatus, + screenReader: `${info.screenReader ? 'yes' : 'no'}`, + remoteData + }; + + + if (!isWindows) { + systemInfo.load = `${osLib.loadavg().map(l => Math.round(l)).join(', ')}`; + } + + return Promise.resolve(systemInfo); + } + + public async getDiagnostics(info: IMainProcessInfo, remoteDiagnostics: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise { + const output: string[] = []; + return listProcesses(info.mainPID).then(async rootProcess => { + + // Environment Info + output.push(''); + output.push(this.formatEnvironment(info)); + + // Process List + output.push(''); + output.push(this.formatProcessList(info, rootProcess)); + + // Workspace Stats + if (info.windows.some(window => window.folderURIs && window.folderURIs.length > 0 && !window.remoteAuthority)) { + output.push(''); + output.push('Workspace Stats: '); + output.push(await this.formatWorkspaceMetadata(info)); + } + + remoteDiagnostics.forEach(diagnostics => { + if (isRemoteDiagnosticError(diagnostics)) { + output.push(`\n${diagnostics.errorMessage}`); + } else { + output.push('\n\n'); + output.push(`Remote: ${diagnostics.hostName}`); + output.push(this.formatMachineInfo(diagnostics.machineInfo)); + + if (diagnostics.processes) { + output.push(this.formatProcessList(info, diagnostics.processes)); + } + + if (diagnostics.workspaceMetadata) { + for (const folder of Object.keys(diagnostics.workspaceMetadata)) { + const metadata = diagnostics.workspaceMetadata[folder]; + + let countMessage = `${metadata.fileCount} files`; + if (metadata.maxFilesReached) { + countMessage = `more than ${countMessage}`; + } + + output.push(`Folder (${folder}): ${countMessage}`); + output.push(this.formatWorkspaceStats(metadata)); + } + } + } + }); + + output.push(''); + output.push(''); + + return output.join('\n'); + }); + } + + private formatWorkspaceStats(workspaceStats: WorkspaceStats): string { + const output: string[] = []; + const lineLength = 60; + let col = 0; + + const appendAndWrap = (name: string, count: number) => { + const item = ` ${name}(${count})`; + + if (col + item.length > lineLength) { + output.push(line); + line = '| '; + col = line.length; + } + else { + col += item.length; + } + line += item; + }; + + // File Types + let line = '| File types:'; + const maxShown = 10; + let max = workspaceStats.fileTypes.length > maxShown ? maxShown : workspaceStats.fileTypes.length; + for (let i = 0; i < max; i++) { + const item = workspaceStats.fileTypes[i]; + appendAndWrap(item.name, item.count); + } + output.push(line); + + // Conf Files + if (workspaceStats.configFiles.length >= 0) { + line = '| Conf files:'; + col = 0; + workspaceStats.configFiles.forEach((item) => { + appendAndWrap(item.name, item.count); + }); + output.push(line); + } + + if (workspaceStats.launchConfigFiles.length > 0) { + let line = '| Launch Configs:'; + workspaceStats.launchConfigFiles.forEach(each => { + const item = each.count > 1 ? ` ${each.name}(${each.count})` : ` ${each.name}`; + line += item; + }); + output.push(line); + } + return output.join('\n'); + } + + private expandGPUFeatures(gpuFeatures: any): string { + const longestFeatureName = Math.max(...Object.keys(gpuFeatures).map(feature => feature.length)); + // Make columns aligned by adding spaces after feature name + return Object.keys(gpuFeatures).map(feature => `${feature}: ${repeat(' ', longestFeatureName - feature.length)} ${gpuFeatures[feature]}`).join('\n '); + } + + private formatWorkspaceMetadata(info: IMainProcessInfo): Promise { + const output: string[] = []; + const workspaceStatPromises: Promise[] = []; + + info.windows.forEach(window => { + if (window.folderURIs.length === 0 || !!window.remoteAuthority) { + return; + } + + output.push(`| Window (${window.title})`); + + window.folderURIs.forEach(uriComponents => { + const folderUri = URI.revive(uriComponents); + if (folderUri.scheme === 'file') { + const folder = folderUri.fsPath; + workspaceStatPromises.push(collectWorkspaceStats(folder, ['node_modules', '.git']).then(stats => { + let countMessage = `${stats.fileCount} files`; + if (stats.maxFilesReached) { + countMessage = `more than ${countMessage}`; + } + output.push(`| Folder (${basename(folder)}): ${countMessage}`); + output.push(this.formatWorkspaceStats(stats)); + + }).catch(error => { + output.push(`| Error: Unable to collect workspace stats for folder ${folder} (${error.toString()})`); + })); + } else { + output.push(`| Folder (${folderUri.toString()}): Workspace stats not available.`); + } + }); + }); + + return Promise.all(workspaceStatPromises) + .then(_ => output.join('\n')) + .catch(e => `Unable to collect workspace stats: ${e}`); + } + + private formatProcessList(info: IMainProcessInfo, rootProcess: ProcessItem): string { + const mapPidToWindowTitle = new Map(); + info.windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title)); + + const output: string[] = []; + + output.push('CPU %\tMem MB\t PID\tProcess'); + + if (rootProcess) { + this.formatProcessItem(info.mainPID, mapPidToWindowTitle, output, rootProcess, 0); + } + + return output.join('\n'); + } + + private formatProcessItem(mainPid: number, mapPidToWindowTitle: Map, output: string[], item: ProcessItem, indent: number): void { + const isRoot = (indent === 0); + + const MB = 1024 * 1024; + + // Format name with indent + let name: string; + if (isRoot) { + name = item.pid === mainPid ? `${product.applicationName} main` : 'remote agent'; + } else { + name = `${repeat(' ', indent)} ${item.name}`; + + if (item.name === 'window') { + name = `${name} (${mapPidToWindowTitle.get(item.pid)})`; + } + } + const memory = process.platform === 'win32' ? item.mem : (osLib.totalmem() * (item.mem / 100)); + output.push(`${pad(Number(item.load.toFixed(0)), 5, ' ')}\t${pad(Number((memory / MB).toFixed(0)), 6, ' ')}\t${pad(Number((item.pid).toFixed(0)), 6, ' ')}\t${name}`); + + // Recurse into children if any + if (Array.isArray(item.children)) { + item.children.forEach(child => this.formatProcessItem(mainPid, mapPidToWindowTitle, output, child, indent + 1)); + } + } + + public async reportWorkspaceStats(workspace: IWorkspace): Promise { + workspace.folders.forEach(folder => { + const folderUri = URI.revive(folder.uri); + if (folderUri.scheme === 'file') { + const folder = folderUri.fsPath; + collectWorkspaceStats(folder, ['node_modules', '.git']).then(stats => { + /* __GDPR__ + "workspace.stats" : { + "fileTypes" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "configTypes" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "launchConfigs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + this.telemetryService.publicLog('workspace.stats', { + fileTypes: stats.fileTypes, + configTypes: stats.configFiles, + launchConfigs: stats.launchConfigFiles + }); + }).catch(_ => { + // Report nothing if collecting metadata fails. + }); + } + }); + } } \ No newline at end of file diff --git a/src/vs/platform/dialogs/browser/dialogService.ts b/src/vs/platform/dialogs/browser/dialogService.ts index d5cc82af884..5a906768029 100644 --- a/src/vs/platform/dialogs/browser/dialogService.ts +++ b/src/vs/platform/dialogs/browser/dialogService.ts @@ -9,7 +9,6 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import Severity from 'vs/base/common/severity'; import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachDialogStyler } from 'vs/platform/theme/common/styler'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -93,5 +92,3 @@ export class DialogService implements IDialogService { return choice; } } - -registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index cb2181914e7..ea57ce91233 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -59,11 +59,6 @@ export interface ISaveDialogOptions { */ defaultUri?: URI; - /** - * If the defaultUri is not provided use the default file name. - */ - defaultFileName?: string; - /** * A set of file filters that are used by the dialog. Each entry is a human readable label, * like "TypeScript", and an array of extensions. diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index b2d41431207..dc75380ba18 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -13,6 +13,7 @@ export interface ParsedArgs { _urls?: string[]; help?: boolean; version?: boolean; + telemetry?: boolean; status?: boolean; wait?: boolean; waitMarkerFilePath?: string; @@ -64,11 +65,12 @@ export interface ParsedArgs { 'max-memory'?: string; 'file-write'?: boolean; 'file-chmod'?: boolean; - 'upload-logs'?: string; 'driver'?: string; 'driver-verbose'?: boolean; remote?: string; 'disable-user-env-probe'?: boolean; + 'enable-remote-auto-shutdown'?: boolean; + 'disable-inspect'?: boolean; } export const IEnvironmentService = createDecorator('environmentService'); @@ -99,6 +101,7 @@ export interface IEnvironmentService { appSettingsHome: URI; settingsResource: URI; keybindingsResource: URI; + keyboardLayoutResource: URI; machineSettingsHome: URI; machineSettingsResource: URI; @@ -153,4 +156,5 @@ export interface IEnvironmentService { driverVerbose: boolean; webviewEndpoint?: string; + readonly webviewResourceRoot: string; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index b3e99e2b808..18cbe48940d 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -8,7 +8,8 @@ import * as os from 'os'; import { localize } from 'vs/nls'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { join } from 'vs/base/common/path'; -import { writeFileSync } from 'vs/base/node/pfs'; +import { statSync, readFileSync } from 'fs'; +import { writeFileSync, readdirSync } from 'vs/base/node/pfs'; /** * This code is also used by standalone cli's. Avoid adding any other dependencies. @@ -41,6 +42,7 @@ export const options: Option[] = [ { id: 'user-data-dir', type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") }, { id: 'version', type: 'boolean', cat: 'o', alias: 'v', description: localize('version', "Print version.") }, { id: 'help', type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") }, + { id: 'telemetry', type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, { id: 'folder-uri', type: 'string', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") }, { id: 'file-uri', type: 'string', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") }, @@ -61,7 +63,6 @@ export const options: Option[] = [ { id: 'inspect-extensions', type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, { id: 'inspect-brk-extensions', type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, { id: 'disable-gpu', type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") }, - { id: 'upload-logs', type: 'string', cat: 't', description: localize('uploadLogs', "Uploads logs from current session to a secure endpoint.") }, { id: 'max-memory', type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") }, { id: 'remote', type: 'string' }, @@ -218,6 +219,41 @@ export function buildVersionMessage(version: string | undefined, commit: string return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`; } +export function buildTelemetryMessage(appRoot: string, extensionsPath: string): string { + // Gets all the directories inside the extension directory + const dirs = readdirSync(extensionsPath).filter(files => { + // This handles case where broken symbolic links can cause statSync to throw and error + try { + return statSync(join(extensionsPath, files)).isDirectory(); + } catch { + return false; + } + }); + const telemetryJsonFolders: string[] = []; + dirs.forEach((dir) => { + const files = readdirSync(join(extensionsPath, dir)).filter(file => file === 'telemetry.json'); + // We know it contains a telemetry.json file so we add it to the list of folders which have one + if (files.length === 1) { + telemetryJsonFolders.push(dir); + } + }); + const mergedTelemetry = Object.create(null); + // Simple function to merge the telemetry into one json object + const mergeTelemetry = (contents: string, dirName: string) => { + const telemetryData = JSON.parse(contents); + mergedTelemetry[dirName] = telemetryData; + }; + telemetryJsonFolders.forEach((folder) => { + const contents = readFileSync(join(extensionsPath, folder, 'telemetry.json')).toString(); + mergeTelemetry(contents, folder); + }); + let contents = readFileSync(join(appRoot, 'telemetry-core.json')).toString(); + mergeTelemetry(contents, 'vscode-core'); + contents = readFileSync(join(appRoot, 'telemetry-extensions.json')).toString(); + mergeTelemetry(contents, 'vscode-extensions'); + return JSON.stringify(mergedTelemetry, null, 4); +} + /** * Converts an argument into an array * @param arg a argument value. Can be undefined, an entry or an array diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index b4f9e1c2061..55c3d8302ae 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -104,6 +104,9 @@ export class EnvironmentService implements IEnvironmentService { return parseUserDataDir(this._args, process); } + @memoize + get webUserDataHome(): URI { return URI.file(parsePathArg(this._args['web-user-data-dir'], process) || this.userDataPath); } + get appNameLong(): string { return product.nameLong; } get appQuality(): string | undefined { return product.quality; } @@ -135,6 +138,9 @@ export class EnvironmentService implements IEnvironmentService { @memoize get keybindingsResource(): URI { return resources.joinPath(this.appSettingsHome, 'keybindings.json'); } + @memoize + get keyboardLayoutResource(): URI { return resources.joinPath(this.appSettingsHome, 'keyboardLayout.json'); } + @memoize get isExtensionDevelopment(): boolean { return !!this._args.extensionDevelopmentPath; } @@ -266,6 +272,10 @@ export class EnvironmentService implements IEnvironmentService { get driverHandle(): string | undefined { return this._args['driver']; } get driverVerbose(): boolean { return !!this._args['driver-verbose']; } + get webviewResourceRoot(): string { + return 'vscode-resource:'; + } + constructor(private _args: ParsedArgs, private _execPath: string) { if (!process.env['VSCODE_LOGS']) { const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); @@ -284,7 +294,7 @@ export function parseSearchPort(args: ParsedArgs, isBuild: boolean): IDebugParam return parseDebugPort(args['inspect-search'], args['inspect-brk-search'], 5876, isBuild); } -export function parseDebugPort(debugArg: string | undefined, debugBrkArg: string | undefined, defaultBuildPort: number, isBuild: boolean, debugId?: string): IExtensionHostDebugParams { +function parseDebugPort(debugArg: string | undefined, debugBrkArg: string | undefined, defaultBuildPort: number, isBuild: boolean, debugId?: string): IExtensionHostDebugParams { const portStr = debugBrkArg || debugArg; const port = Number(portStr) || (!isBuild ? defaultBuildPort : null); const brk = port ? Boolean(!!debugBrkArg) : false; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 8b024db44b2..833fedd1fcc 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -196,8 +196,8 @@ export interface IExtensionManagementService { zip(extension: ILocalExtension): Promise; unzip(zipLocation: URI, type: ExtensionType): Promise; - install(vsix: URI): Promise; - installFromGallery(extension: IGalleryExtension): Promise; + install(vsix: URI): Promise; + installFromGallery(extension: IGalleryExtension): Promise; uninstall(extension: ILocalExtension, force?: boolean): Promise; reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType): Promise; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts index 963e304601e..b88350f44c7 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from '../common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; @@ -78,7 +78,9 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer _serviceBrand: any; - constructor(private channel: IChannel) { } + constructor( + private readonly channel: IChannel, + ) { } get onInstallExtension(): Event { return this.channel.listen('onInstallExtension'); } get onDidInstallExtension(): Event { return Event.map(this.channel.listen('onDidInstallExtension'), i => ({ ...i, local: i.local ? transformIncomingExtension(i.local, null) : i.local })); } @@ -93,12 +95,12 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer return Promise.resolve(this.channel.call('unzip', [zipLocation, type])); } - install(vsix: URI): Promise { - return Promise.resolve(this.channel.call('install', [vsix])); + install(vsix: URI): Promise { + return Promise.resolve(this.channel.call('install', [vsix])).then(local => transformIncomingExtension(local, null)); } - installFromGallery(extension: IGalleryExtension): Promise { - return Promise.resolve(this.channel.call('installFromGallery', [extension])); + installFromGallery(extension: IGalleryExtension): Promise { + return Promise.resolve(this.channel.call('installFromGallery', [extension])).then(local => transformIncomingExtension(local, null)); } uninstall(extension: ILocalExtension, force = false): Promise { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 36fc28f7e00..e09049c5b94 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -109,7 +109,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private uninstalledFileLimiter: Queue; private reportedExtensions: Promise | undefined; private lastReportTimestamp = 0; - private readonly installingExtensions: Map> = new Map>(); + private readonly installingExtensions: Map> = new Map>(); private readonly uninstallingExtensions: Map> = new Map>(); private readonly manifestCache: ExtensionsManifestCache; private readonly extensionLifecycle: ExtensionsLifecycle; @@ -158,7 +158,7 @@ export class ExtensionManagementService extends Disposable implements IExtension unzip(zipLocation: URI, type: ExtensionType): Promise { this.logService.trace('ExtensionManagementService#unzip', zipLocation.toString()); - return this.install(zipLocation, type); + return this.install(zipLocation, type).then(local => local.identifier); } private collectFiles(extension: ILocalExtension): Promise { @@ -187,7 +187,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } - install(vsix: URI, type: ExtensionType = ExtensionType.User): Promise { + install(vsix: URI, type: ExtensionType = ExtensionType.User): Promise { this.logService.trace('ExtensionManagementService#install', vsix.toString()); return createCancelablePromise(token => { return this.downloadVsix(vsix).then(downloadLocation => { @@ -222,7 +222,7 @@ export class ExtensionManagementService extends Disposable implements IExtension metadata => this.installFromZipPath(identifierWithVersion, zipPath, metadata, type, operation, token), () => this.installFromZipPath(identifierWithVersion, zipPath, null, type, operation, token)) .then( - () => { this.logService.info('Successfully installed the extension:', identifier.id); return identifier; }, + local => { this.logService.info('Successfully installed the extension:', identifier.id); return local; }, e => { this.logService.error('Failed to install the extension:', identifier.id, e.message); return Promise.reject(e); @@ -264,7 +264,10 @@ export class ExtensionManagementService extends Disposable implements IExtension )); } - async installFromGallery(extension: IGalleryExtension): Promise { + async installFromGallery(extension: IGalleryExtension): Promise { + if (!this.galleryService.isEnabled()) { + return Promise.reject(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"))); + } const startTime = new Date().getTime(); const onDidInstallExtensionSuccess = (extension: IGalleryExtension, operation: InstallOperation, local: ILocalExtension) => { @@ -298,7 +301,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this._onInstallExtension.fire({ identifier: extension.identifier, gallery: extension }); let operation: InstallOperation = InstallOperation.Install; - let cancellationToken: CancellationToken, successCallback: (a?: any) => void, errorCallback: (e?: any) => any | null; + let cancellationToken: CancellationToken, successCallback: (local: ILocalExtension) => void, errorCallback: (e?: any) => any | null; cancellablePromise = createCancelablePromise(token => { cancellationToken = token; return new Promise((c, e) => { successCallback = c; errorCallback = e; }); }); this.installingExtensions.set(key, cancellablePromise); try { @@ -320,7 +323,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } this.installingExtensions.delete(key); onDidInstallExtensionSuccess(extension, operation, local); - successCallback(null); + successCallback(local); }, error => { this.installingExtensions.delete(key); @@ -450,7 +453,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } this.logService.info('Installation completed.', identifier.id); if (metadata) { - local.metadata = metadata; + this.setMetadata(local, metadata); return this.saveMetadataForLocalExtension(local); } return local; @@ -780,12 +783,21 @@ export class ExtensionManagementService extends Disposable implements IExtension const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)) : null; const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0]; const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)) : null; - const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: metadata ? metadata.id : null }; - return { type, identifier, manifest, metadata, location: URI.file(extensionPath), readmeUrl, changelogUrl }; + const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; + const local = { type, identifier, manifest, metadata, location: URI.file(extensionPath), readmeUrl, changelogUrl }; + if (metadata) { + this.setMetadata(local, metadata); + } + return local; })) .then(undefined, () => null); } + private setMetadata(local: ILocalExtension, metadata: IGalleryMetadata): void { + local.metadata = metadata; + local.identifier.uuid = metadata.id; + } + async removeDeprecatedExtensions(): Promise { await this.removeUninstalledExtensions(); await this.removeOutdatedExtensions(); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index ac73dd3de19..69b1fb785d4 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -208,7 +208,7 @@ export interface IFileSystemProvider { readonly capabilities: FileSystemProviderCapabilities; readonly onDidChangeCapabilities: Event; - readonly onDidErrorOccur?: Event; // TODO@ben remove once file watchers are solid + readonly onDidErrorOccur?: Event; // TODO@ben remove once file watchers are solid readonly onDidChangeFile: Event; watch(resource: URI, opts: IWatchOptions): IDisposable; diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index b794f8ffc8a..5fc1f5464f1 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -9,14 +9,13 @@ import { parseArgs } from 'vs/platform/environment/node/argv'; import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/common/issue'; import { BrowserWindow, ipcMain, screen, Event, dialog } from 'electron'; import { ILaunchService } from 'vs/platform/launch/electron-main/launchService'; -import { PerformanceInfo, IDiagnosticsService } from 'vs/platform/diagnostics/electron-main/diagnosticsService'; +import { PerformanceInfo, IDiagnosticsService, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWindowState } from 'vs/platform/windows/electron-main/windows'; import { listProcesses } from 'vs/base/node/ps'; -import { isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; @@ -40,10 +39,14 @@ export class IssueService implements IIssueService { } private registerListeners(): void { - ipcMain.on('vscode:issueSystemInfoRequest', (event: Event) => { - this.diagnosticsService.getSystemInfo(this.launchService).then(msg => { - event.sender.send('vscode:issueSystemInfoResponse', msg); - }); + ipcMain.on('vscode:issueSystemInfoRequest', async (event: Event) => { + Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) + .then(result => { + const [info, remoteData] = result; + this.diagnosticsService.getSystemInfo(info, remoteData).then(msg => { + event.sender.send('vscode:issueSystemInfoResponse', msg); + }); + }); }); ipcMain.on('vscode:listProcesses', async (event: Event) => { @@ -262,8 +265,12 @@ export class IssueService implements IIssueService { }); } - public getSystemStatus(): Promise { - return this.diagnosticsService.getDiagnostics(this.launchService); + public async getSystemStatus(): Promise { + return Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) + .then(result => { + const [info, remoteData] = result; + return this.diagnosticsService.getDiagnostics(info, remoteData); + }); } private getWindowPosition(parentWindow: BrowserWindow, defaultWidth: number, defaultHeight: number): IWindowState { @@ -335,14 +342,18 @@ export class IssueService implements IIssueService { } private getPerformanceInfo(): Promise { - return new Promise((resolve, reject) => { - this.diagnosticsService.getPerformanceInfo(this.launchService) - .then(diagnosticInfo => { - resolve(diagnosticInfo); - }) - .catch(err => { - this.logService.warn('issueService#getPerformanceInfo ', err.message); - reject(err); + return new Promise(async (resolve, reject) => { + Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) + .then(result => { + const [info, remoteData] = result; + this.diagnosticsService.getPerformanceInfo(info, remoteData) + .then(diagnosticInfo => { + resolve(diagnosticInfo); + }) + .catch(err => { + this.logService.warn('issueService#getPerformanceInfo ', err.message); + reject(err); + }); }); }); } diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index e5d4a3f979a..2273af1f010 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -57,6 +57,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[]; public abstract _dumpDebugInfo(): string; + public abstract _dumpDebugInfoJSON(): string; public getDefaultKeybindingsContent(): string { return ''; diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 22777e7a552..57b2f701fce 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -93,5 +93,6 @@ export interface IKeybindingService { mightProducePrintableCharacter(event: IKeyboardEvent): boolean; _dumpDebugInfo(): string; + _dumpDebugInfoJSON(): string; } diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 2bc7f54bef9..3c30daf3ab4 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, Keybinding, SimpleKeybinding, createKeybinding, KeyMod } from 'vs/base/common/keyCodes'; -import { OS, OperatingSystem, isWeb } from 'vs/base/common/platform'; +import { KeyCode, Keybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; import { CommandsRegistry, ICommandHandler, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -130,22 +130,18 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { const actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule); if (actualKb && actualKb.primary) { - if (!this._assertBrowserConflicts(actualKb.primary, rule.id)) { - const kk = createKeybinding(actualKb.primary, OS); - if (kk) { - this._registerDefaultKeybinding(kk, rule.id, undefined, rule.weight, 0, rule.when); - } + const kk = createKeybinding(actualKb.primary, OS); + if (kk) { + this._registerDefaultKeybinding(kk, rule.id, undefined, rule.weight, 0, rule.when); } } if (actualKb && Array.isArray(actualKb.secondary)) { for (let i = 0, len = actualKb.secondary.length; i < len; i++) { const k = actualKb.secondary[i]; - if (!this._assertBrowserConflicts(k, rule.id)) { - const kk = createKeybinding(k, OS); - if (kk) { - this._registerDefaultKeybinding(kk, rule.id, undefined, rule.weight, -i - 1, rule.when); - } + const kk = createKeybinding(k, OS); + if (kk) { + this._registerDefaultKeybinding(kk, rule.id, undefined, rule.weight, -i - 1, rule.when); } } } @@ -212,54 +208,6 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { } } - private _assertBrowserConflicts(keybinding: number, commandId: string): boolean { - if (!isWeb) { - return false; - } - - const firstPart = (keybinding & 0x0000FFFF) >>> 0; - const chordPart = (keybinding & 0xFFFF0000) >>> 16; - const modifiersMask = KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift; - - for (let part of [firstPart, chordPart]) { - if ((part & modifiersMask) === 0) { - continue; - } - - if ((part & modifiersMask) === KeyMod.CtrlCmd && (part & 0x000000FF) === KeyCode.KEY_W) { - console.warn('Ctrl/Cmd+W keybindings should not be used by default in web. Offender: ', keybinding, ' for ', commandId); - - return true; - } - - if ((part & modifiersMask) === KeyMod.CtrlCmd && (part & 0x000000FF) === KeyCode.KEY_N) { - console.warn('Ctrl/Cmd+N keybindings should not be used by default in web. Offender: ', keybinding, ' for ', commandId); - - return true; - } - - if ((part & modifiersMask) === KeyMod.CtrlCmd && (part & 0x000000FF) === KeyCode.KEY_T) { - console.warn('Ctrl/Cmd+T keybindings should not be used by default in web. Offender: ', keybinding, ' for ', commandId); - - return true; - } - - if ((part & modifiersMask) === (KeyMod.CtrlCmd | KeyMod.Alt) && ((part & 0x000000FF) === KeyCode.LeftArrow || (part & 0x000000FF) === KeyCode.RightArrow)) { - console.warn('Ctrl/Cmd+Arrow keybindings should not be used by default in web. Offender: ', keybinding, ' for ', commandId); - - return true; - } - - if ((part & modifiersMask) === KeyMod.CtrlCmd && ((part & 0x000000FF) >= KeyCode.KEY_0 && (part & 0x000000FF) <= KeyCode.KEY_9)) { - console.warn('Ctrl/Cmd+Num keybindings should not be used by default in web. Offender: ', keybinding, ' for ', commandId); - - return true; - } - } - - return false; - } - private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, commandArgs: any, weight1: number, weight2: number, when: ContextKeyExpr | null | undefined): void { if (OS === OperatingSystem.Windows) { this._assertNoCtrlAlt(keybinding.parts[0], commandId); diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 7ddc222b5ca..4798e84518d 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -83,6 +83,10 @@ suite('AbstractKeybindingService', () => { public _dumpDebugInfo(): string { return ''; } + + public _dumpDebugInfoJSON(): string { + return ''; + } } let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!; diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 239cc99568d..6a318210521 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -137,4 +137,8 @@ export class MockKeybindingService implements IKeybindingService { public _dumpDebugInfo(): string { return ''; } + + public _dumpDebugInfoJSON(): string { + return ''; + } } diff --git a/src/vs/platform/launch/common/launchService.ts b/src/vs/platform/launch/common/launchService.ts new file mode 100644 index 00000000000..0c0e1db2134 --- /dev/null +++ b/src/vs/platform/launch/common/launchService.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { UriComponents } from 'vs/base/common/uri'; + +export interface IWindowInfo { + pid: number; + title: string; + folderURIs: UriComponents[]; + remoteAuthority?: string; +} + +export interface IMainProcessInfo { + mainPID: number; + // All arguments after argv[0], the exec path + mainArguments: string[]; + windows: IWindowInfo[]; + screenReader: boolean; + gpuFeatureStatus: any; +} \ No newline at end of file diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index d5d81a70bf8..c192151f915 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -14,12 +14,13 @@ import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-m import { whenDeleted } from 'vs/base/node/pfs'; import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { BrowserWindow, ipcMain, Event as IpcEvent } from 'electron'; +import { URI } from 'vs/base/common/uri'; +import { BrowserWindow, ipcMain, Event as IpcEvent, app } from 'electron'; import { Event } from 'vs/base/common/event'; import { hasArgs } from 'vs/platform/environment/node/argv'; import { coalesce } from 'vs/base/common/arrays'; import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/common/launchService'; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); @@ -29,20 +30,6 @@ export interface IStartArguments { userEnv: IProcessEnvironment; } -export interface IWindowInfo { - pid: number; - title: string; - folderURIs: UriComponents[]; - remoteAuthority?: string; -} - -export interface IMainProcessInfo { - mainPID: number; - // All arguments after argv[0], the exec path - mainArguments: string[]; - windows: IWindowInfo[]; -} - export interface IRemoteDiagnosticOptions { includeProcesses?: boolean; includeWorkspaceMetadata?: boolean; @@ -280,7 +267,9 @@ export class LaunchService implements ILaunchService { return Promise.resolve({ mainPID: process.pid, mainArguments: process.argv.slice(1), - windows + windows, + screenReader: app.isAccessibilitySupportEnabled(), + gpuFeatureStatus: app.getGPUFeatureStatus() }); } diff --git a/src/vs/platform/lifecycle/browser/lifecycleService.ts b/src/vs/platform/lifecycle/browser/lifecycleService.ts new file mode 100644 index 00000000000..b0c3014986f --- /dev/null +++ b/src/vs/platform/lifecycle/browser/lifecycleService.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ShutdownReason, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; +import { localize } from 'vs/nls'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class BrowserLifecycleService extends AbstractLifecycleService { + + _serviceBrand: ServiceIdentifier; + + constructor( + @ILogService readonly logService: ILogService + ) { + super(logService); + + this.registerListeners(); + } + + private registerListeners(): void { + window.onbeforeunload = () => this.beforeUnload(); + } + + private beforeUnload(): string | null { + let veto: boolean = false; + + // Before Shutdown + this._onBeforeShutdown.fire({ + veto(value) { + if (value === true) { + veto = true; + } else if (value instanceof Promise && !veto) { + console.warn(new Error('Long running onBeforeShutdown currently not supported')); + } + }, + reason: ShutdownReason.QUIT + }); + + // Veto: signal back to browser by returning a non-falsify return value + if (veto) { + return localize('lifecycleVeto', "Changes that you made may not be saved. Please check press 'Cancel' and try again."); + } + + // No Veto: continue with Will Shutdown + this._onWillShutdown.fire({ + join() { + console.warn(new Error('Long running onWillShutdown currently not supported')); + }, + reason: ShutdownReason.QUIT + }); + + return null; + } +} \ No newline at end of file diff --git a/src/vs/platform/lifecycle/common/lifecycleService.ts b/src/vs/platform/lifecycle/common/lifecycleService.ts index a82dd63da41..b325cde418a 100644 --- a/src/vs/platform/lifecycle/common/lifecycleService.ts +++ b/src/vs/platform/lifecycle/common/lifecycleService.ts @@ -9,10 +9,11 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ILifecycleService, BeforeShutdownEvent, WillShutdownEvent, StartupKind, LifecyclePhase, LifecyclePhaseToString } from 'vs/platform/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { mark } from 'vs/base/common/performance'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export abstract class AbstractLifecycleService extends Disposable implements ILifecycleService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; protected readonly _onBeforeShutdown = this._register(new Emitter()); get onBeforeShutdown(): Event { return this._onBeforeShutdown.event; } diff --git a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts index aa7f6442e08..ecef5c02772 100644 --- a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { ShutdownReason, StartupKind, handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; +import { ShutdownReason, StartupKind, handleVetos, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ipcRenderer as ipc } from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -12,12 +12,13 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { onUnexpectedError } from 'vs/base/common/errors'; import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class LifecycleService extends AbstractLifecycleService { private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason'; - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private shutdownReason: ShutdownReason; diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts index a56ee98dec6..058efdc2500 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts @@ -131,7 +131,7 @@ export const enum LifecycleMainPhase { export class LifecycleService extends Disposable implements ILifecycleService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 3017aeda736..c885cca5e51 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -29,7 +29,7 @@ import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTr import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; import { AsyncDataTree, IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; -import { IKeyboardNavigationEventFilter, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; +import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export type ListWidget = List | PagedList | ITree | ObjectTree | DataTree | AsyncDataTree; @@ -114,6 +114,7 @@ export const horizontalScrollingKey = 'workbench.list.horizontalScrolling'; export const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation'; export const automaticKeyboardNavigationSettingKey = 'workbench.list.automaticKeyboardNavigation'; const treeIndentKey = 'workbench.tree.indent'; +const treeRenderIndentGuidesKey = 'workbench.tree.renderIndentGuides'; function getHorizontalScrollingSetting(configurationService: IConfigurationService): boolean { return getMigratedSettingValue(configurationService, horizontalScrollingKey, 'workbench.tree.horizontalScrolling'); @@ -897,6 +898,7 @@ function workbenchTreeDataPreamble( ...computeStyles(themeService.getTheme(), defaultListStyles), ...workbenchListOptions, indent: configurationService.getValue(treeIndentKey), + renderIndentGuides: configurationService.getValue(treeRenderIndentGuidesKey), automaticKeyboardNavigation: getAutomaticKeyboardNavigation(), simpleKeyboardNavigation: keyboardNavigation === 'simple', filterOnType: keyboardNavigation === 'filter', @@ -977,6 +979,10 @@ class WorkbenchTreeInternals { const indent = configurationService.getValue(treeIndentKey); tree.updateOptions({ indent }); } + if (e.affectsConfiguration(treeRenderIndentGuidesKey)) { + const renderIndentGuides = configurationService.getValue(treeRenderIndentGuidesKey); + tree.updateOptions({ renderIndentGuides }); + } if (e.affectsConfiguration(keyboardNavigationSettingKey)) { updateKeyboardNavigation(); } @@ -1053,6 +1059,12 @@ configurationRegistry.registerConfiguration({ maximum: 40, 'description': localize('tree indent setting', "Controls tree indentation in pixels.") }, + [treeRenderIndentGuidesKey]: { + type: 'string', + enum: ['none', 'onHover', 'always'], + default: 'onHover', + description: localize('render tree indent guides', "Controls whether the tree should render indent guides.") + }, [keyboardNavigationSettingKey]: { 'type': 'string', 'enum': ['simple', 'highlight', 'filter'], diff --git a/src/vs/platform/log/node/logIpc.ts b/src/vs/platform/log/common/logIpc.ts similarity index 100% rename from src/vs/platform/log/node/logIpc.ts rename to src/vs/platform/log/common/logIpc.ts diff --git a/src/vs/platform/product/browser/productService.ts b/src/vs/platform/product/browser/productService.ts new file mode 100644 index 00000000000..084144d009f --- /dev/null +++ b/src/vs/platform/product/browser/productService.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IProductService, IProductConfiguration } from 'vs/platform/product/common/product'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class ProductService implements IProductService { + + private readonly productConfiguration: IProductConfiguration | null; + + constructor() { + const element = document.getElementById('vscode-remote-product-configuration'); + this.productConfiguration = element ? JSON.parse(element.getAttribute('data-settings')!) : null; + } + + _serviceBrand: ServiceIdentifier; + + get version(): string { return '1.35.0'; } + + get commit(): string | undefined { return undefined; } + + get nameLong(): string { return ''; } + + get urlProtocol(): string { return ''; } + + get extensionAllowedProposedApi(): string[] { return this.productConfiguration ? this.productConfiguration.extensionAllowedProposedApi : []; } + + get uiExtensions(): string[] | undefined { return this.productConfiguration ? this.productConfiguration.uiExtensions : undefined; } + + get enableTelemetry(): boolean { return false; } + + get sendASmile(): { reportIssueUrl: string, requestFeatureUrl: string } | undefined { return this.productConfiguration ? this.productConfiguration.sendASmile : undefined; } + + get extensionsGallery() { return this.productConfiguration ? this.productConfiguration.extensionsGallery : undefined; } +} \ No newline at end of file diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 2a35190f8ae..97f3fe3e5a4 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -30,4 +30,89 @@ export interface IProductService { reportIssueUrl: string; requestFeatureUrl: string; }; +} + +export interface IProductConfiguration { + nameShort: string; + nameLong: string; + applicationName: string; + win32AppId: string; + win32x64AppId: string; + win32UserAppId: string; + win32x64UserAppId: string; + win32AppUserModelId: string; + win32MutexName: string; + darwinBundleIdentifier: string; + urlProtocol: string; + dataFolderName: string; + downloadUrl: string; + updateUrl?: string; + quality?: string; + target?: string; + commit?: string; + settingsSearchBuildId?: number; + settingsSearchUrl?: string; + experimentsUrl?: string; + date: string; + extensionsGallery?: { + serviceUrl: string; + itemUrl: string; + controlUrl: string; + recommendationsUrl: string; + }; + extensionTips: { [id: string]: string; }; + extensionImportantTips: { [id: string]: { name: string; pattern: string; }; }; + exeBasedExtensionTips: { [id: string]: { friendlyName: string, windowsPath?: string, recommendations: string[] }; }; + extensionKeywords: { [extension: string]: string[]; }; + extensionAllowedBadgeProviders: string[]; + extensionAllowedProposedApi: string[]; + keymapExtensionTips: string[]; + crashReporter: { + companyName: string; + productName: string; + }; + welcomePage: string; + enableTelemetry: boolean; + aiConfig: { + asimovKey: string; + }; + sendASmile: { + reportIssueUrl: string, + requestFeatureUrl: string + }; + documentationUrl: string; + releaseNotesUrl: string; + keyboardShortcutsUrlMac: string; + keyboardShortcutsUrlLinux: string; + keyboardShortcutsUrlWin: string; + introductoryVideosUrl: string; + tipsAndTricksUrl: string; + newsletterSignupUrl: string; + twitterUrl: string; + requestFeatureUrl: string; + reportIssueUrl: string; + licenseUrl: string; + privacyStatementUrl: string; + telemetryOptOutUrl: string; + npsSurveyUrl: string; + surveys: ISurveyData[]; + checksums: { [path: string]: string; }; + checksumFailMoreInfoUrl: string; + hockeyApp: { + 'win32-ia32': string; + 'win32-x64': string; + 'linux-x64': string; + 'darwin': string; + }; + logUploaderUrl: string; + portable?: string; + uiExtensions?: string[]; +} + +export interface ISurveyData { + surveyId: string; + surveyUrl: string; + languageId: string; + editCount: number; + userProbability: number; } \ No newline at end of file diff --git a/src/vs/platform/product/node/product.ts b/src/vs/platform/product/node/product.ts index 6a5cf7880e8..08610d71a1a 100644 --- a/src/vs/platform/product/node/product.ts +++ b/src/vs/platform/product/node/product.ts @@ -5,91 +5,7 @@ import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; - -export interface IProductConfiguration { - nameShort: string; - nameLong: string; - applicationName: string; - win32AppId: string; - win32x64AppId: string; - win32UserAppId: string; - win32x64UserAppId: string; - win32AppUserModelId: string; - win32MutexName: string; - darwinBundleIdentifier: string; - urlProtocol: string; - dataFolderName: string; - downloadUrl: string; - updateUrl?: string; - quality?: string; - target?: string; - commit?: string; - settingsSearchBuildId?: number; - settingsSearchUrl?: string; - experimentsUrl?: string; - date: string; - extensionsGallery?: { - serviceUrl: string; - itemUrl: string; - controlUrl: string; - recommendationsUrl: string; - }; - extensionTips: { [id: string]: string; }; - extensionImportantTips: { [id: string]: { name: string; pattern: string; }; }; - exeBasedExtensionTips: { [id: string]: { friendlyName: string, windowsPath?: string, recommendations: string[] }; }; - extensionKeywords: { [extension: string]: string[]; }; - extensionAllowedBadgeProviders: string[]; - extensionAllowedProposedApi: string[]; - keymapExtensionTips: string[]; - crashReporter: { - companyName: string; - productName: string; - }; - welcomePage: string; - enableTelemetry: boolean; - aiConfig: { - asimovKey: string; - }; - sendASmile: { - reportIssueUrl: string, - requestFeatureUrl: string - }; - documentationUrl: string; - releaseNotesUrl: string; - keyboardShortcutsUrlMac: string; - keyboardShortcutsUrlLinux: string; - keyboardShortcutsUrlWin: string; - introductoryVideosUrl: string; - tipsAndTricksUrl: string; - newsletterSignupUrl: string; - twitterUrl: string; - requestFeatureUrl: string; - reportIssueUrl: string; - licenseUrl: string; - privacyStatementUrl: string; - telemetryOptOutUrl: string; - npsSurveyUrl: string; - surveys: ISurveyData[]; - checksums: { [path: string]: string; }; - checksumFailMoreInfoUrl: string; - hockeyApp: { - 'win32-ia32': string; - 'win32-x64': string; - 'linux-x64': string; - 'darwin': string; - }; - logUploaderUrl: string; - portable?: string; - uiExtensions?: string[]; -} - -export interface ISurveyData { - surveyId: string; - surveyUrl: string; - languageId: string; - editCount: number; - userProbability: number; -} +import { IProductConfiguration } from 'vs/platform/product/common/product'; const rootPath = path.dirname(getPathFromAmdModule(require, '')); const productJsonPath = path.join(rootPath, 'product.json'); diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 1e1162f640f..634b38b1929 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -20,15 +20,7 @@ export interface IProgressService { withProgress(options: IProgressOptions | IProgressNotificationOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise; } -export const ILocalProgressService = createDecorator('localProgressService'); - -/** - * A progress service that will report progress local to the UI piece triggered from. E.g. - * if used from an action of a viewlet, the progress will be reported in that viewlet. - */ -export interface ILocalProgressService { - - _serviceBrand: ServiceIdentifier; +export interface IProgressIndicator { /** * Show progress customized with the provided flags. @@ -132,7 +124,7 @@ export class LongRunningOperation extends Disposable { private currentProgressTimeout: any; constructor( - private localProgressService: ILocalProgressService + private progressIndicator: IProgressIndicator ) { super(); } @@ -147,7 +139,7 @@ export class LongRunningOperation extends Disposable { const newOperationToken = new CancellationTokenSource(); this.currentProgressTimeout = setTimeout(() => { if (newOperationId === this.currentOperationId) { - this.currentProgressRunner = this.localProgressService.show(true); + this.currentProgressRunner = this.progressIndicator.show(true); } }, progressDelay); @@ -173,3 +165,13 @@ export class LongRunningOperation extends Disposable { } } } + +export const IEditorProgressService = createDecorator('editorProgressService'); + +/** + * A progress service that will report progress local to the editor triggered from. + */ +export interface IEditorProgressService extends IProgressIndicator { + + _serviceBrand: ServiceIdentifier; +} diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index fdc022b18d9..edc89fb407e 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -27,6 +27,9 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF private readonly _onDidChange = this._register(new Emitter()); readonly onDidChangeFile: Event = this._onDidChange.event; + private _onDidWatchErrorOccur: Emitter = this._register(new Emitter()); + get onDidErrorOccur(): Event { return this._onDidWatchErrorOccur.event; } + private readonly _onDidChangeCapabilities = this._register(new Emitter()); readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; @@ -43,8 +46,14 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF } private registerListeners(): void { - this._register(this.channel.listen('filechange', [this.session])((events) => { - this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type }))); + this._register(this.channel.listen('filechange', [this.session])((eventsOrError) => { + if (Array.isArray(eventsOrError)) { + const events = eventsOrError; + this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type }))); + } else { + const error = eventsOrError; + this._onDidWatchErrorOccur.fire(error); + } })); } diff --git a/src/vs/platform/sign/browser/signService.ts b/src/vs/platform/sign/browser/signService.ts index 46447b5e923..501ab8939d4 100644 --- a/src/vs/platform/sign/browser/signService.ts +++ b/src/vs/platform/sign/browser/signService.ts @@ -11,6 +11,6 @@ export class SignService implements ISignService { _serviceBrand: ServiceIdentifier; async sign(value: string): Promise { - return Promise.resolve((self).WINDOW_CONFIGURATION.connectionAuthToken); + return Promise.resolve(document.getElementById('vscode-remote-connection-token')!.getAttribute('data-settings')!); } } \ No newline at end of file diff --git a/src/vs/platform/telemetry/common/gdprTypings.ts b/src/vs/platform/telemetry/common/gdprTypings.ts new file mode 100644 index 00000000000..4fa946fb59f --- /dev/null +++ b/src/vs/platform/telemetry/common/gdprTypings.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +export interface IPropertyData { + classification: 'SystemMetaData' | 'CallstackOrException' | 'CustomerContent' | 'PublicNonPersonalData'; + purpose: 'PerformanceAndHealth' | 'FeatureInsight' | 'BusinessInsight'; + endpoint?: string; + isMeasurement?: boolean; +} + +export interface IGDPRProperty { + readonly [name: string]: IPropertyData | undefined | IGDPRProperty; +} + +export type ClassifiedEvent = { + [k in keyof T]: any +}; + +export type StrictPropertyChecker = keyof TEvent extends keyof TClassifiedEvent ? keyof TClassifiedEvent extends keyof TEvent ? TEvent : TError : TError; + +export type StrictPropertyCheckError = 'Type of classified event does not match event properties'; + +export type StrictPropertyCheck = StrictPropertyChecker, StrictPropertyCheckError>; + +export type GDPRClassification = { [_ in keyof T]: IPropertyData | IGDPRProperty | undefined }; \ No newline at end of file diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index 41924be4901..e82693979be 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; export const ITelemetryService = createDecorator('telemetryService'); @@ -29,6 +30,8 @@ export interface ITelemetryService { */ publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise; + publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean): Promise; + setEnabled(value: boolean): void; getTelemetryInfo(): Promise; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index fce980fcb9a..cab7686a859 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -13,6 +13,7 @@ import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/co import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { cloneAndChange, mixin } from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; export interface ITelemetryServiceConfig { appender: ITelemetryAppender; @@ -131,6 +132,10 @@ export class TelemetryService implements ITelemetryService { }); } + publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean): Promise { + return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths); + } + private _cleanupInfo(stack: string, anonymizeFilePaths?: boolean): string { let updatedStack = stack; diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 69e17c36bd1..e2284474798 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -8,12 +8,16 @@ import { IConfigurationService, ConfigurationTarget, ConfigurationTargetToString import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { ILogService } from 'vs/platform/log/common/log'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; export const NullTelemetryService = new class implements ITelemetryService { _serviceBrand: undefined; publicLog(eventName: string, data?: ITelemetryData) { return Promise.resolve(undefined); } + publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as ITelemetryData); + } setEnabled() { } isOptedIn: true; getTelemetryInfo(): Promise { @@ -175,6 +179,7 @@ const configurationValueWhitelist = [ 'terminal.integrated.fontFamily', 'window.openFilesInNewWindow', 'window.restoreWindows', + 'window.nativeFullScreen', 'window.zoomLevel', 'workbench.editor.enablePreview', 'workbench.editor.enablePreviewFromQuickOpen', diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 3739dbf7936..1e292909050 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -238,6 +238,7 @@ export const listWarningForeground = registerColor('list.warningForeground', { d export const listFilterWidgetBackground = registerColor('listFilterWidget.background', { light: '#efc1ad', dark: '#653723', hc: Color.black }, nls.localize('listFilterWidgetBackground', 'Background color of the type filter widget in lists and trees.')); export const listFilterWidgetOutline = registerColor('listFilterWidget.outline', { dark: Color.transparent, light: Color.transparent, hc: '#f38518' }, nls.localize('listFilterWidgetOutline', 'Outline color of the type filter widget in lists and trees.')); export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget.noMatchesOutline', { dark: '#BE1100', light: '#BE1100', hc: contrastBorder }, nls.localize('listFilterWidgetNoMatchesOutline', 'Outline color of the type filter widget in lists and trees, when there are no matches.')); +export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hc: '#a9a9a9' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); export const pickerGroupForeground = registerColor('pickerGroup.foreground', { dark: '#3794FF', light: '#0066BF', hc: Color.white }, nls.localize('pickerGroupForeground', "Quick picker color for grouping labels.")); export const pickerGroupBorder = registerColor('pickerGroup.border', { dark: '#3F3F46', light: '#CCCEDB', hc: Color.white }, nls.localize('pickerGroupBorder', "Quick picker color for grouping borders.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index c000ffdac15..3cf776cf532 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -227,6 +227,7 @@ export interface IListStyleOverrides extends IStyleOverrides { listFilterWidgetOutline?: ColorIdentifier; listFilterWidgetNoMatchesOutline?: ColorIdentifier; listMatchesShadow?: ColorIdentifier; + treeIndentGuidesStroke?: ColorIdentifier; } export function attachListStyler(widget: IThemable, themeService: IThemeService, overrides?: IListStyleOverrides): IDisposable { @@ -252,7 +253,8 @@ export const defaultListStyles: IColorMapping = { listFilterWidgetBackground: listFilterWidgetBackground, listFilterWidgetOutline: listFilterWidgetOutline, listFilterWidgetNoMatchesOutline: listFilterWidgetNoMatchesOutline, - listMatchesShadow: widgetShadow + listMatchesShadow: widgetShadow, + treeIndentGuidesStroke }; export interface IButtonStyleOverrides extends IStyleOverrides { diff --git a/src/vs/platform/update/node/update.config.contribution.ts b/src/vs/platform/update/node/update.config.contribution.ts index 541ae043f97..b6ef43b29fa 100644 --- a/src/vs/platform/update/node/update.config.contribution.ts +++ b/src/vs/platform/update/node/update.config.contribution.ts @@ -6,6 +6,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { localize } from 'vs/nls'; +import { isWindows } from 'vs/base/common/platform'; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ @@ -38,8 +39,9 @@ configurationRegistry.registerConfiguration({ type: 'boolean', default: true, scope: ConfigurationScope.APPLICATION, - description: localize('enableWindowsBackgroundUpdates', "Enables Windows background updates. The updates are fetched from a Microsoft online service."), - tags: ['usesOnlineServices'] + title: localize('enableWindowsBackgroundUpdatesTitle', "Enable Background Updates on Windows"), + description: localize('enableWindowsBackgroundUpdates', "Enable to download and install new VS Code Versions in the background on Windows"), + included: isWindows }, 'update.showReleaseNotes': { type: 'boolean', diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 6110a4b6c70..19abdf93184 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -153,6 +153,7 @@ export interface IWindowsService { // Global methods openWindow(windowId: number, uris: IURIToOpen[], options: IOpenSettings): Promise; openNewWindow(options?: INewWindowOptions): Promise; + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; getWindowCount(): Promise; log(severity: string, ...messages: string[]): Promise; @@ -238,7 +239,7 @@ export interface IWindowService { closeWorkspace(): Promise; updateTouchBar(items: ISerializableCommandAction[][]): Promise; enterWorkspace(path: URI): Promise; - toggleFullScreen(): Promise; + toggleFullScreen(target?: HTMLElement): Promise; setRepresentedFilename(fileName: string): Promise; getRecentlyOpened(): Promise; focusWindow(): Promise; @@ -299,7 +300,7 @@ export function getTitleBarStyle(configurationService: IConfigurationService, en return 'native'; // native tabs on sierra do not work with custom title style } - const useSimpleFullScreen = isMacintosh && configuration.nativeFullScreen === false; + const useSimpleFullScreen = false; //isMacintosh && configuration.nativeFullScreen === false; if (useSimpleFullScreen) { return 'native'; // simple fullscreen does not work well with custom title style (https://github.com/Microsoft/vscode/issues/63291) } diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index 73acbb0f67a..34ec58fc49a 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -12,6 +12,7 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export class WindowsService implements IWindowsService { @@ -195,6 +196,10 @@ export class WindowsService implements IWindowsService { return this.channel.call('openNewWindow', options); } + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); + } + async getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { const result = await this.channel.call<{ id: number; diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index ed90804f293..da1b56c7ee9 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -21,7 +21,7 @@ import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platf import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { Schemas } from 'vs/base/common/network'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; @@ -306,6 +306,18 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH this.windowsMainService.openNewWindow(OpenContext.API, options); } + async openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + this.logService.trace('windowsService#openExtensionDevelopmentHostWindow ' + JSON.stringify(args)); + + if (args.extensionDevelopmentPath) { + this.windowsMainService.openExtensionDevelopmentHostWindow(args.extensionDevelopmentPath, { + context: OpenContext.API, + cli: args, + userEnv: Object.keys(env).length > 0 ? env : undefined + }); + } + } + async getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { this.logService.trace('windowsService#getWindows'); diff --git a/src/vs/platform/windows/node/windowsIpc.ts b/src/vs/platform/windows/node/windowsIpc.ts index c12c47b5a3a..6fbad27a549 100644 --- a/src/vs/platform/windows/node/windowsIpc.ts +++ b/src/vs/platform/windows/node/windowsIpc.ts @@ -102,6 +102,7 @@ export class WindowsChannel implements IServerChannel { return this.service.openWindow(arg[0], urisToOpen, options); } case 'openNewWindow': return this.service.openNewWindow(arg); + case 'openExtensionDevelopmentHostWindow': return this.service.openExtensionDevelopmentHostWindow(arg[0], arg[1]); case 'getWindows': return this.service.getWindows(); case 'getWindowCount': return this.service.getWindowCount(); case 'relaunch': return this.service.relaunch(arg[0]); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 8b9b6fdb05e..175557c976c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2070,7 +2070,7 @@ declare module 'vscode' { /** * An array of diagnostics. */ - readonly diagnostics: Diagnostic[]; + readonly diagnostics: ReadonlyArray; /** * Requested kind of actions to return. @@ -3544,6 +3544,15 @@ declare module 'vscode' { */ target?: Uri; + /** + * The tooltip text when you hover over this link. + * + * If a tooltip is provided, is will be displayed in a string that includes instructions on how to + * trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS, + * user settings, and localization. + */ + tooltip?: string; + /** * Creates a new document link. * @@ -4665,6 +4674,23 @@ declare module 'vscode' { dispose(): void; } + /** + * In a remote window the extension kind describes if an extension + * runs where the UI (window) runs or if an extension runs remotely. + */ + export enum ExtensionKind { + + /** + * Extension runs where the UI runs. + */ + UI = 1, + + /** + * Extension runs where the remote extension host runs. + */ + Workspace = 2 + } + /** * Represents an extension. * @@ -4692,6 +4718,15 @@ declare module 'vscode' { */ readonly packageJSON: any; + /** + * The extension kind describes if an extension runs where the UI runs + * or if an extension runs where the remote extension host runs. The extension kind + * if defined in the `package.json` file of extensions but can also be refined + * via the the `remote.extensionKind`-setting. When no remote extension host exists, + * the value is [`ExtensionKind.UI`](#ExtensionKind.UI). + */ + extensionKind: ExtensionKind; + /** * The public API exported by this extension. It is an invalid action * to access this field before this extension has been activated. @@ -6033,6 +6068,17 @@ declare module 'vscode' { */ export const sessionId: string; + /** + * The name of a remote. Defined by extensions, popular samples are `wsl` for the Windows + * Subsystem for Linux or `ssh-remote` for remotes using a secure shell. + * + * *Note* that the value is `undefined` when there is no remote extension host but that the + * value is defined in all extension hosts (local and remote) in case a remote extension host + * exists. Use [`Extension#extensionKind`](#Extension.extensionKind) to know if + * a specific extension runs remote or not. + */ + export const remoteName: string | undefined; + /** * Opens an *external* item, e.g. a http(s) or mailto-link, using the * default application. @@ -6996,7 +7042,7 @@ declare module 'vscode' { * interaction is needed. Note that the terminals will still be exposed to all extensions * as normal. */ - runInBackground?: boolean; + hideFromUser?: boolean; } /** @@ -8948,7 +8994,7 @@ declare module 'vscode' { /** * All extensions currently known to the system. */ - export let all: Extension[]; + export const all: ReadonlyArray>; /** * An event which fires when `extensions.all` changes. This can happen when extensions are diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index eebd9a47091..60b108c952c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -17,23 +17,16 @@ declare module 'vscode' { //#region Joh - ExecutionContext - + // THIS is a deprecated proposal export enum ExtensionExecutionContext { Local = 1, Remote = 2 } - export interface ExtensionContext { - /** - * Describes the context in which this extension is executed, e.g. - * a Node.js-context on the same machine or on a remote machine - */ executionContext: ExtensionExecutionContext; } - //#endregion - //#region Joh - call hierarchy export enum CallHierarchyDirection { @@ -139,14 +132,15 @@ declare module 'vscode' { export interface WebviewEditorInset { readonly editor: TextEditor; - readonly range: Range; + readonly line: number; + readonly height: number; readonly webview: Webview; readonly onDidDispose: Event; dispose(): void; } export namespace window { - export function createWebviewTextEditorInset(editor: TextEditor, range: Range, options?: WebviewOptions): WebviewEditorInset; + export function createWebviewTextEditorInset(editor: TextEditor, line: number, height: number, options?: WebviewOptions): WebviewEditorInset; } //#endregion @@ -1127,6 +1121,18 @@ declare module 'vscode' { readonly onDidWriteData: Event; } + + export interface TerminalOptions { + /** + * When enabled the terminal will run the process as normal but not be surfaced to the user + * until `Terminal.show` is called. The typical usage for this is when you need to run + * something that may need interactivity but only want to tell the user about it when + * interaction is needed. Note that the terminals will still be exposed to all extensions + * as normal. + */ + runInBackground?: boolean; + } + /** * Represents the dimensions of a terminal. */ @@ -1417,22 +1423,6 @@ declare module 'vscode' { //#endregion - //#region DocumentLink tooltip mjbvz - - interface DocumentLink { - /** - * The tooltip text when you hover over this link. - * - * If a tooltip is provided, is will be displayed in a string that includes instructions on how to - * trigger the link, such as `cmd + click to {0}`. The specific instructions vary depending on OS, - * user settings, and localization. - */ - tooltip?: string; - } - - // #endregion - - // #region Ben - status bar item with ID and Name export namespace window { @@ -1480,4 +1470,39 @@ declare module 'vscode' { } //#endregion + + //#region Webview Resource Roots + + export interface Webview { + /** + * Root url from which local resources are loaded inside of webviews. + * + * This is `vscode-resource:` when vscode is run on the desktop. When vscode is run + * on the web, this points to a server endpoint. + */ + readonly resourceRoot: Thenable; + } + + //#endregion + + + //#region Joh - read/write files of any scheme + + export interface FileSystem { + stat(uri: Uri): Thenable; + readDirectory(uri: Uri): Thenable<[string, FileType][]>; + createDirectory(uri: Uri): Thenable; + readFile(uri: Uri): Thenable; + writeFile(uri: Uri, content: Uint8Array, options?: { create: boolean, overwrite: boolean }): Thenable; + delete(uri: Uri, options?: { recursive: boolean }): Thenable; + rename(source: Uri, target: Uri, options?: { overwrite: boolean }): Thenable; + copy(source: Uri, target: Uri, options?: { overwrite: boolean }): Thenable; + } + + export namespace workspace { + + export const fs: FileSystem; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index e0cbafd3463..e06a5dc3ea9 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -7,12 +7,12 @@ import { UriComponents, URI } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { MainContext, MainThreadEditorInsetsShape, IExtHostContext, ExtHostEditorInsetsShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from '../common/extHostCustomers'; -import { IRange } from 'vs/editor/common/core/range'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/common/webview'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; // todo@joh move these things back into something like contrib/insets class EditorWebviewZone implements IViewZone { @@ -32,13 +32,15 @@ class EditorWebviewZone implements IViewZone { constructor( readonly editor: IActiveCodeEditor, - readonly range: IRange, + readonly line: number, + readonly height: number, readonly webview: Webview, ) { this.domNode = document.createElement('div'); - this.afterLineNumber = range.startLineNumber; - this.afterColumn = range.startColumn; - this.heightInLines = range.endLineNumber - range.startLineNumber; + this.domNode.style.zIndex = '10'; // without this, the webview is not interactive + this.afterLineNumber = line; + this.afterColumn = 1; + this.heightInLines = height; editor.changeViewZones(accessor => this._id = accessor.addZone(this)); webview.mountTo(this.domNode); @@ -58,6 +60,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { constructor( context: IExtHostContext, + @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ICodeEditorService private readonly _editorService: ICodeEditorService, @IWebviewService private readonly _webviewService: IWebviewService, ) { @@ -68,7 +71,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { this._disposables.dispose(); } - async $createEditorInset(handle: number, id: string, uri: UriComponents, range: IRange, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise { + async $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise { let editor: IActiveCodeEditor | undefined; id = id.substr(0, id.indexOf(',')); //todo@joh HACK @@ -92,10 +95,11 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { allowSvgs: false, extension: { id: extensionId, location: URI.revive(extensionLocation) } }, { - allowScripts: options.enableScripts + allowScripts: options.enableScripts, + localResourceRoots: options.localResourceRoots ? options.localResourceRoots.map(uri => URI.revive(uri)) : undefined }); - const webviewZone = new EditorWebviewZone(editor, range, webview); + const webviewZone = new EditorWebviewZone(editor, line, height, webview); const remove = () => { disposables.dispose(); @@ -142,4 +146,8 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { } return Promise.resolve(false); } + + async $getResourceRoot(_handle: number): Promise { + return this._environmentService.webviewResourceRoot; + } } diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 61871e2c39b..cb84e3c1323 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -5,8 +5,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions } from 'vs/platform/files/common/files'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileStat } from 'vs/platform/files/common/files'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../common/extHost.protocol'; import { ResourceLabelFormatter, ILabelService } from 'vs/platform/label/common/label'; @@ -60,6 +60,56 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { } fileProvider.$onFileSystemChange(changes); } + + + // --- + + async $stat(uri: UriComponents): Promise { + const stat = await this._fileService.resolve(URI.revive(uri), { resolveMetadata: true }); + return { + ctime: 0, + mtime: stat.mtime, + size: stat.size, + type: MainThreadFileSystem._getFileType(stat) + }; + } + + async $readdir(uri: UriComponents): Promise<[string, FileType][]> { + const stat = await this._fileService.resolve(URI.revive(uri), { resolveMetadata: false }); + if (!stat.children) { + throw new Error('not a folder'); + } + return stat.children.map(child => [child.name, MainThreadFileSystem._getFileType(child)]); + } + + private static _getFileType(stat: IFileStat): FileType { + return (stat.isDirectory ? FileType.Directory : FileType.File) + (stat.isSymbolicLink ? FileType.SymbolicLink : 0); + } + + async $readFile(uri: UriComponents): Promise { + return (await this._fileService.readFile(URI.revive(uri))).value; + } + + async $writeFile(uri: UriComponents, content: VSBuffer, opts: FileWriteOptions): Promise { + //todo@joh honor opts + await this._fileService.writeFile(URI.revive(uri), content, {}); + } + + async $rename(source: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise { + await this._fileService.move(URI.revive(source), URI.revive(target), opts.overwrite); + } + + async $copy(source: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise { + await this._fileService.copy(URI.revive(source), URI.revive(target), opts.overwrite); + } + + async $mkdir(uri: UriComponents): Promise { + await this._fileService.createFolder(URI.revive(uri)); + } + + async $delete(uri: UriComponents, opts: FileDeleteOptions): Promise { + await this._fileService.del(URI.revive(uri), opts); + } } class RemoteFileSystemProvider implements IFileSystemProvider { diff --git a/src/vs/workbench/api/browser/mainThreadTelemetry.ts b/src/vs/workbench/api/browser/mainThreadTelemetry.ts index 41f9947bc71..3b800af7284 100644 --- a/src/vs/workbench/api/browser/mainThreadTelemetry.ts +++ b/src/vs/workbench/api/browser/mainThreadTelemetry.ts @@ -6,6 +6,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { MainThreadTelemetryShape, MainContext, IExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; @extHostNamedCustomer(MainContext.MainThreadTelemetry) export class MainThreadTelemetry implements MainThreadTelemetryShape { @@ -28,4 +29,10 @@ export class MainThreadTelemetry implements MainThreadTelemetryShape { data[MainThreadTelemetry._name] = true; this._telemetryService.publicLog(eventName, data); } + + $publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data: StrictPropertyCheck): void { + this.$publicLog(eventName, data as any); + } } + + diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index a68310bb3ee..b975524b5f6 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { UriComponents, URI } from 'vs/base/common/uri'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -22,11 +24,15 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape constructor( extHostContext: IExtHostContext, - @ITerminalService private readonly terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService, + @ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService, + @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._remoteAuthority = extHostContext.remoteAuthority; - this._toDispose.push(terminalService.onInstanceCreated((instance) => { + + // ITerminalService listeners + this._toDispose.push(_terminalService.onInstanceCreated((instance) => { // Delay this message so the TerminalInstance constructor has a chance to finish and // return the ID normally to the extension host. The ID that is passed here will be used // to register non-extension API terminals in the extension host. @@ -35,25 +41,32 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._onInstanceDimensionsChanged(instance); }, EXT_HOST_CREATION_DELAY); })); - this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance))); - this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance))); - this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance))); - this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request))); - this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null))); - this._toDispose.push(terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title))); - this._toDispose.push(terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed))); + this._toDispose.push(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance))); + this._toDispose.push(_terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance))); + this._toDispose.push(_terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance))); + this._toDispose.push(_terminalService.onInstanceMaximumDimensionsChanged(instance => this._onInstanceMaximumDimensionsChanged(instance))); + this._toDispose.push(_terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request))); + this._toDispose.push(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null))); + this._toDispose.push(_terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title))); + this._toDispose.push(_terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed))); + this._toDispose.push(_terminalService.onRequestAvailableShells(e => this._onRequestAvailableShells(e))); + + // ITerminalInstanceService listeners + if (terminalInstanceService.onRequestDefaultShellAndArgs) { + this._toDispose.push(terminalInstanceService.onRequestDefaultShellAndArgs(e => this._onRequestDefaultShellAndArgs(e))); + } // Set initial ext host state - this.terminalService.terminalInstances.forEach(t => { + this._terminalService.terminalInstances.forEach(t => { this._onTerminalOpened(t); t.processReady.then(() => this._onTerminalProcessIdReady(t)); }); - const activeInstance = this.terminalService.getActiveInstance(); + const activeInstance = this._terminalService.getActiveInstance(); if (activeInstance) { this._proxy.$acceptActiveTerminalChanged(activeInstance.id); } - this.terminalService.extHostReady(extHostContext.remoteAuthority); + this._terminalService.extHostReady(extHostContext.remoteAuthority); } public dispose(): void { @@ -63,7 +76,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // when the extension host process goes down ? } - public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean, runInBackground?: boolean): Promise<{ id: number, name: string }> { + public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean, hideFromUser?: boolean): Promise<{ id: number, name: string }> { const shellLaunchConfig: IShellLaunchConfig = { name, executable: shellPath, @@ -73,9 +86,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape ignoreConfigurationCwd: true, env, strictEnv, - runInBackground + hideFromUser }; - const terminal = this.terminalService.createTerminal(shellLaunchConfig); + const terminal = this._terminalService.createTerminal(shellLaunchConfig); return Promise.resolve({ id: terminal.id, name: terminal.title @@ -83,55 +96,55 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $createTerminalRenderer(name: string): Promise { - const instance = this.terminalService.createTerminalRenderer(name); + const instance = this._terminalService.createTerminalRenderer(name); return Promise.resolve(instance.id); } public $show(terminalId: number, preserveFocus: boolean): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { - this.terminalService.setActiveInstance(terminalInstance); - this.terminalService.showPanel(!preserveFocus); + this._terminalService.setActiveInstance(terminalInstance); + this._terminalService.showPanel(!preserveFocus); } } public $hide(terminalId: number): void { - const instance = this.terminalService.getActiveInstance(); + const instance = this._terminalService.getActiveInstance(); if (instance && instance.id === terminalId) { - this.terminalService.hidePanel(); + this._terminalService.hidePanel(); } } public $dispose(terminalId: number): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { terminalInstance.dispose(); } } public $terminalRendererWrite(terminalId: number, text: string): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { terminalInstance.write(text); } } public $terminalRendererSetName(terminalId: number, name: string): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { terminalInstance.setTitle(name, false); } } public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { terminalInstance.setDimensions(dimensions); } } public $terminalRendererRegisterOnInputListener(terminalId: number): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (!terminalInstance) { return; } @@ -147,14 +160,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $sendText(terminalId: number, text: string, addNewLine: boolean): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { terminalInstance.sendText(text, addNewLine); } } public $registerOnDataListener(terminalId: number): void { - const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (!terminalInstance) { return; } @@ -216,6 +229,10 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._proxy.$acceptTerminalDimensions(instance.id, instance.cols, instance.rows); } + private _onInstanceMaximumDimensionsChanged(instance: ITerminalInstance): void { + this._proxy.$acceptTerminalMaximumDimensions(instance.id, instance.maxCols, instance.maxRows); + } + private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void { // Only allow processes on remote ext hosts if (!this._remoteAuthority) { @@ -275,4 +292,25 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } this._terminalProcesses[terminalId].emitLatency(sum / COUNT); } + + private _isPrimaryExtHost(): boolean { + // The "primary" ext host is the remote ext host if there is one, otherwise the local + const conn = this._remoteAgentService.getConnection(); + if (conn) { + return this._remoteAuthority === conn.remoteAuthority; + } + return true; + } + + private _onRequestAvailableShells(request: IAvailableShellsRequest): void { + if (this._isPrimaryExtHost()) { + this._proxy.$requestAvailableShells().then(e => request(e)); + } + } + + private _onRequestDefaultShellAndArgs(request: IDefaultShellAndArgsRequest): void { + if (this._isPrimaryExtHost()) { + this._proxy.$requestDefaultShellAndArgs().then(e => request(e.shell, e.args)); + } + } } diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index f41c0327ae3..5dd98e6e0d6 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -23,6 +23,7 @@ import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/commo import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { extHostNamedCustomer } from '../common/extHostCustomers'; import { IProductService } from 'vs/platform/product/common/product'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @extHostNamedCustomer(MainContext.MainThreadWebviews) export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape { @@ -54,6 +55,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews @IOpenerService private readonly _openerService: IOpenerService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IProductService private readonly _productService: IProductService, + @IEnvironmentService private readonly _environmentService: IEnvironmentService, ) { super(); @@ -75,7 +77,8 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews })); this._register(lifecycleService.onBeforeShutdown(e => { - e.veto(this._onBeforeShutdown()); + this._onBeforeShutdown(); + e.veto(false); // Don't veto shutdown }, this)); } @@ -138,6 +141,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews webview.setOptions(reviveWebviewOptions(options as any /*todo@mat */)); } + async $getResourceRoot(_handle: WebviewPanelHandle): Promise { + return this._environmentService.webviewResourceRoot; + } + public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void { const webview = this.getWebview(handle); if (webview.isDisposed()) { @@ -217,13 +224,12 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return `mainThreadWebview-${viewType}`; } - private _onBeforeShutdown(): boolean { + private _onBeforeShutdown(): void { this._webviews.forEach((webview) => { if (!webview.isDisposed() && webview.state && this._revivers.has(webview.state.viewType)) { webview.state.state = webview.webviewState; } }); - return false; // Don't veto shutdown } private createWebviewEventDelegate(handle: WebviewPanelHandle) { diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 4c5b038c231..533dbb62eae 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -11,6 +11,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape, IOpenUriOptions } from '../common/extHost.protocol'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { extractLocalHostUriMetaDataForPortMapping } from 'vs/workbench/contrib/webview/common/portMapping'; @extHostNamedCustomer(MainContext.MainThreadWindow) export class MainThreadWindow implements MainThreadWindowShape { @@ -48,26 +49,16 @@ export class MainThreadWindow implements MainThreadWindowShape { async $openUri(uriComponent: UriComponents, options: IOpenUriOptions): Promise { let uri = URI.revive(uriComponent); if (options.allowTunneling && !!this.environmentService.configuration.remoteAuthority) { - if (uri.scheme === 'http' || uri.scheme === 'https') { - const port = this.getLocalhostPort(uri); - if (typeof port === 'number') { - const tunnel = await this.getOrCreateTunnel(port); - if (tunnel) { - uri = uri.with({ authority: `localhost:${tunnel.tunnelLocalPort}` }); - } + const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); + if (portMappingRequest) { + const tunnel = await this.getOrCreateTunnel(portMappingRequest.port); + if (tunnel) { + uri = uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }); } } } - return this.windowsService.openExternal(encodeURI(uri.toString(true))); - } - - private getLocalhostPort(uri: URI): number | undefined { - const match = /^localhost:(\d+)$/.exec(uri.authority); - if (match) { - return +match[1]; - } - return undefined; + return this.windowsService.openExternal(uri.toString()); } private getOrCreateTunnel(remotePort: number): Promise | undefined { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index bb93e419568..2f51c54ee7c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -46,6 +46,7 @@ import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHi import { IRelativePattern } from 'vs/base/common/glob'; import { IRemoteConsoleLog } from 'vs/base/common/console'; import { VSBuffer } from 'vs/base/common/buffer'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -84,7 +85,7 @@ export interface IInitData { logLevel: LogLevel; logsLocation: URI; autoStart: boolean; - remoteAuthority?: string | null; + remote: { isRemote: boolean; authority: string | undefined; }; } export interface IConfigurationInitData extends IConfigurationData { @@ -389,7 +390,7 @@ export interface MainThreadProgressShape extends IDisposable { } export interface MainThreadTerminalServiceShape extends IDisposable { - $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string | null }, waitOnExit?: boolean, strictEnv?: boolean, runInBackground?: boolean): Promise<{ id: number, name: string }>; + $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string | null }, waitOnExit?: boolean, strictEnv?: boolean, hideFromUser?: boolean): Promise<{ id: number, name: string }>; $createTerminalRenderer(name: string): Promise; $dispose(terminalId: number): void; $hide(terminalId: number): void; @@ -507,15 +508,17 @@ export interface MainThreadStorageShape extends IDisposable { export interface MainThreadTelemetryShape extends IDisposable { $publicLog(eventName: string, data?: any): void; + $publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck): void; } export interface MainThreadEditorInsetsShape extends IDisposable { - $createEditorInset(handle: number, id: string, uri: UriComponents, range: IRange, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise; + $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise; $disposeEditorInset(handle: number): void; $setHtml(handle: number, value: string): void; $setOptions(handle: number, options: modes.IWebviewOptions): void; $postMessage(handle: number, value: any): Promise; + $getResourceRoot(handle: number): Promise; } export interface ExtHostEditorInsetsShape { @@ -540,6 +543,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $setHtml(handle: WebviewPanelHandle, value: string): void; $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void; $postMessage(handle: WebviewPanelHandle, value: any): Promise; + $getResourceRoot(handle: WebviewPanelHandle): Promise; $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; @@ -591,6 +595,15 @@ export interface MainThreadFileSystemShape extends IDisposable { $registerResourceLabelFormatter(handle: number, formatter: ResourceLabelFormatter): void; $unregisterResourceLabelFormatter(handle: number): void; $onFileSystemChange(handle: number, resource: IFileChangeDto[]): void; + + $stat(uri: UriComponents): Promise; + $readdir(resource: UriComponents): Promise<[string, files.FileType][]>; + $readFile(resource: UriComponents): Promise; + $writeFile(resource: UriComponents, content: VSBuffer, opts: files.FileWriteOptions): Promise; + $rename(resource: UriComponents, target: UriComponents, opts: files.FileOverwriteOptions): Promise; + $copy(resource: UriComponents, target: UriComponents, opts: files.FileOverwriteOptions): Promise; + $mkdir(resource: UriComponents): Promise; + $delete(resource: UriComponents, opts: files.FileDeleteOptions): Promise; } export interface MainThreadSearchShape extends IDisposable { @@ -1106,6 +1119,16 @@ export interface ShellLaunchConfigDto { env?: { [key: string]: string | null }; } +export interface IShellDefinitionDto { + label: string; + path: string; +} + +export interface IShellAndArgsDto { + shell: string; + args: string[] | string | undefined; +} + export interface ExtHostTerminalServiceShape { $acceptTerminalClosed(id: number): void; $acceptTerminalOpened(id: number, name: string): void; @@ -1115,6 +1138,7 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalRendererInput(id: number, data: string): void; $acceptTerminalTitleChange(id: number, name: string): void; $acceptTerminalDimensions(id: number, cols: number, rows: number): void; + $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void; $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; $acceptProcessInput(id: number, data: string): void; $acceptProcessResize(id: number, cols: number, rows: number): void; @@ -1123,6 +1147,8 @@ export interface ExtHostTerminalServiceShape { $acceptProcessRequestCwd(id: number): void; $acceptProcessRequestLatency(id: number): number; $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; + $requestAvailableShells(): Promise; + $requestDefaultShellAndArgs(): Promise; } export interface ExtHostSCMShape { diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 2da50e9c3f4..4d78f9c67f3 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -18,6 +18,7 @@ import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeature import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter } from './apiCommands'; import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; +import { IRange } from 'vs/editor/common/core/range'; export class ExtHostApiCommands { @@ -414,15 +415,21 @@ export class ExtHostApiCommands { }); } - private _executeSelectionRangeProvider(resource: URI, positions: types.Position[]): Promise { + private _executeSelectionRangeProvider(resource: URI, positions: types.Position[]): Promise { const pos = positions.map(typeConverters.Position.from); const args = { resource, position: pos[0], positions: pos }; - return this._commands.executeCommand('_executeSelectionRangeProvider', args).then(result => { - return result.map(oneResult => oneResult.map(typeConverters.SelectionRange.to)); + return this._commands.executeCommand('_executeSelectionRangeProvider', args).then(result => { + return result.map(ranges => { + let node: types.SelectionRange | undefined; + for (const range of ranges.reverse()) { + node = new types.SelectionRange(typeConverters.Range.to(range), node); + } + return node!; + }); }); } diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts index 975f4ec205d..5b40af3d04d 100644 --- a/src/vs/workbench/api/common/extHostCodeInsets.ts +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; -import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as vscode from 'vscode'; -import { MainThreadEditorInsetsShape } from './extHost.protocol'; +import { MainThreadEditorInsetsShape, ExtHostEditorInsetsShape } from './extHost.protocol'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -export class ExtHostEditorInsets implements ExtHostEditorInsets { +export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { private _handlePool = 0; private _disposables = new DisposableStore(); @@ -39,7 +38,7 @@ export class ExtHostEditorInsets implements ExtHostEditorInsets { this._disposables.dispose(); } - createWebviewEditorInset(editor: vscode.TextEditor, range: vscode.Range, options: vscode.WebviewOptions | undefined, extension: IExtensionDescription): vscode.WebviewEditorInset { + createWebviewEditorInset(editor: vscode.TextEditor, line: number, height: number, options: vscode.WebviewOptions | undefined, extension: IExtensionDescription): vscode.WebviewEditorInset { let apiEditor: ExtHostTextEditor | undefined; for (const candidate of this._editors.getVisibleTextEditors()) { @@ -62,6 +61,10 @@ export class ExtHostEditorInsets implements ExtHostEditorInsets { private _html: string = ''; private _options: vscode.WebviewOptions; + get resourceRoot(): Promise { + return that._proxy.$getResourceRoot(handle); + } + set options(value: vscode.WebviewOptions) { this._options = value; that._proxy.$setOptions(handle, value); @@ -92,7 +95,8 @@ export class ExtHostEditorInsets implements ExtHostEditorInsets { const inset = new class implements vscode.WebviewEditorInset { readonly editor: vscode.TextEditor = editor; - readonly range: vscode.Range = range; + readonly line: number = line; + readonly height: number = height; readonly webview: vscode.Webview = webview; readonly onDidDispose: vscode.Event = onDidDispose.event; @@ -109,7 +113,7 @@ export class ExtHostEditorInsets implements ExtHostEditorInsets { } }; - this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.document.uri, typeConverters.Range.from(range), options || {}, extension.identifier, extension.extensionLocation); + this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.document.uri, line + 1, height, options || {}, extension.identifier, extension.extensionLocation); this._insets.set(handle, { editor, inset, onDidReceiveMessage }); return inset; diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 5173ebb176c..3d0840068b0 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -210,7 +210,7 @@ export class CommandsConverter { this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } - toInternal2(command: vscode.Command | undefined, disposables: DisposableStore): CommandDto | undefined { + toInternal(command: vscode.Command | undefined, disposables: DisposableStore): CommandDto | undefined { if (!command) { return undefined; @@ -240,40 +240,6 @@ export class CommandsConverter { return result; } - toInternal(command: vscode.Command): CommandDto; - toInternal(command: undefined): undefined; - toInternal(command: vscode.Command | undefined): CommandDto | undefined; - toInternal(command: vscode.Command | undefined): CommandDto | undefined { - - if (!command) { - return undefined; - } - - const result: CommandDto = { - $ident: undefined, - id: command.command, - title: command.title, - }; - - if (command.command && isNonEmptyArray(command.arguments)) { - // we have a contributed command with arguments. that - // means we don't want to send the arguments around - - const id = ++this._cachIdPool; - this._cache.set(id, command); - result.$ident = id; - - result.id = this._delegatingCommandId; - result.arguments = [id]; - } - - if (command.tooltip) { - result.tooltip = command.tooltip; - } - - return result; - } - fromInternal(command: modes.Command): vscode.Command | undefined { const id = ObjectIdentifier.of(command); diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 4b76a7bece0..93b58afbd08 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -650,9 +650,9 @@ export class ExtHostCommentThread implements vscode.CommentThread { const label = this.label; const contextValue = this.contextValue; const comments = this._comments.map(cmt => { return convertToModeComment2(this, this._commentController, cmt, this._commandsConverter, this._commentsMap, this._acceptInputDisposables.value!); }); - const acceptInputCommand = this._acceptInputCommand ? this._commandsConverter.toInternal2(this._acceptInputCommand, this._acceptInputDisposables.value) : undefined; - const additionalCommands = (this._additionalCommands ? this._additionalCommands.map(x => this._commandsConverter.toInternal2(x, this._acceptInputDisposables.value!)) : []) as CommandDto[]; - const deleteCommand = this._deleteCommand ? this._commandsConverter.toInternal2(this._deleteCommand, this._acceptInputDisposables.value) : undefined; + const acceptInputCommand = this._acceptInputCommand ? this._commandsConverter.toInternal(this._acceptInputCommand, this._acceptInputDisposables.value) : undefined; + const additionalCommands = (this._additionalCommands ? this._additionalCommands.map(x => this._commandsConverter.toInternal(x, this._acceptInputDisposables.value!)) : []) as CommandDto[]; + const deleteCommand = this._deleteCommand ? this._commandsConverter.toInternal(this._deleteCommand, this._acceptInputDisposables.value) : undefined; const collapsibleState = convertToCollapsibleState(this._collapseState); this._proxy.$updateCommentThread( @@ -951,9 +951,9 @@ function convertToModeComment2(thread: ExtHostCommentThread, commentController: userName: vscodeComment.author ? vscodeComment.author.name : vscodeComment.userName, userIconPath: iconPath, isDraft: vscodeComment.isDraft, - selectCommand: vscodeComment.selectCommand ? commandsConverter.toInternal2(vscodeComment.selectCommand, disposables) : undefined, - editCommand: vscodeComment.editCommand ? commandsConverter.toInternal2(vscodeComment.editCommand, disposables) : undefined, - deleteCommand: vscodeComment.deleteCommand ? commandsConverter.toInternal2(vscodeComment.deleteCommand, disposables) : undefined, + selectCommand: vscodeComment.selectCommand ? commandsConverter.toInternal(vscodeComment.selectCommand, disposables) : undefined, + editCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.editCommand, disposables) : undefined, + deleteCommand: vscodeComment.deleteCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand, disposables) : undefined, label: vscodeComment.label, commentReactions: reactions ? reactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction)) : undefined }; @@ -971,7 +971,7 @@ function convertToComment(provider: vscode.DocumentCommentProvider | vscode.Work userIconPath: iconPath, canEdit: canEdit, canDelete: canDelete, - selectCommand: vscodeComment.command ? commandsConverter.toInternal2(vscodeComment.command, disposables) : undefined, + selectCommand: vscodeComment.command ? commandsConverter.toInternal(vscodeComment.command, disposables) : undefined, isDraft: vscodeComment.isDraft, commentReactions: vscodeComment.commentReactions ? vscodeComment.commentReactions.map(reaction => convertToReaction(provider, reaction)) : undefined }; diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index 86d49261456..8ba9dbd8162 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -12,8 +12,9 @@ import { ExtensionActivationError, MissingDependencyError } from 'vs/workbench/s const NO_OP_VOID_PROMISE = Promise.resolve(undefined); export interface IExtensionMemento { + get(key: string): T | undefined; get(key: string, defaultValue: T): T; - update(key: string, value: any): Promise; + update(key: string, value: any): Promise; } export interface IExtensionContext { diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index a46c3fd8417..778a5cb67ad 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -104,6 +104,36 @@ class FsLinkProvider { } } +class ConsumerFileSystem implements vscode.FileSystem { + + constructor(private _proxy: MainThreadFileSystemShape) { } + + stat(uri: vscode.Uri): Promise { + return this._proxy.$stat(uri); + } + readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { + return this._proxy.$readdir(uri); + } + createDirectory(uri: vscode.Uri): Promise { + return this._proxy.$mkdir(uri); + } + async readFile(uri: vscode.Uri): Promise { + return (await this._proxy.$readFile(uri)).buffer; + } + writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; } = { create: true, overwrite: true }): Promise { + return this._proxy.$writeFile(uri, VSBuffer.wrap(content), options); + } + delete(uri: vscode.Uri, options: { recursive: boolean; } = { recursive: false }): Promise { + return this._proxy.$delete(uri, { ...options, useTrash: false }); //todo@joh useTrash + } + rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; } = { overwrite: false }): Promise { + return this._proxy.$rename(oldUri, newUri, options); + } + copy(source: vscode.Uri, destination: vscode.Uri, options: { overwrite: boolean } = { overwrite: false }): Promise { + return this._proxy.$copy(source, destination, options); + } +} + export class ExtHostFileSystem implements ExtHostFileSystemShape { private readonly _proxy: MainThreadFileSystemShape; @@ -113,9 +143,10 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { private readonly _watches = new Map(); private _linkProviderRegistration: IDisposable; - // Used as a handle both for file system providers and resource label formatters (being lazy) private _handlePool: number = 0; + readonly fileSystem: vscode.FileSystem; + constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures) { this._proxy = mainContext.getProxy(MainContext.MainThreadFileSystem); this._usedSchemes.add(Schemas.file); @@ -128,6 +159,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { this._usedSchemes.add(Schemas.mailto); this._usedSchemes.add(Schemas.data); this._usedSchemes.add(Schemas.command); + + this.fileSystem = new ConsumerFileSystem(this._proxy); } dispose(): void { @@ -223,31 +256,31 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } $stat(handle: number, resource: UriComponents): Promise { - return Promise.resolve(this.getProvider(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat); + return Promise.resolve(this._getFsProvider(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat); } $readdir(handle: number, resource: UriComponents): Promise<[string, files.FileType][]> { - return Promise.resolve(this.getProvider(handle).readDirectory(URI.revive(resource))); + return Promise.resolve(this._getFsProvider(handle).readDirectory(URI.revive(resource))); } $readFile(handle: number, resource: UriComponents): Promise { - return Promise.resolve(this.getProvider(handle).readFile(URI.revive(resource))).then(data => VSBuffer.wrap(data)); + return Promise.resolve(this._getFsProvider(handle).readFile(URI.revive(resource))).then(data => VSBuffer.wrap(data)); } $writeFile(handle: number, resource: UriComponents, content: VSBuffer, opts: files.FileWriteOptions): Promise { - return Promise.resolve(this.getProvider(handle).writeFile(URI.revive(resource), content.buffer, opts)); + return Promise.resolve(this._getFsProvider(handle).writeFile(URI.revive(resource), content.buffer, opts)); } $delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): Promise { - return Promise.resolve(this.getProvider(handle).delete(URI.revive(resource), opts)); + return Promise.resolve(this._getFsProvider(handle).delete(URI.revive(resource), opts)); } $rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): Promise { - return Promise.resolve(this.getProvider(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts)); + return Promise.resolve(this._getFsProvider(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts)); } $copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): Promise { - const provider = this.getProvider(handle); + const provider = this._getFsProvider(handle); if (!provider.copy) { throw new Error('FileSystemProvider does not implement "copy"'); } @@ -255,11 +288,11 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } $mkdir(handle: number, resource: UriComponents): Promise { - return Promise.resolve(this.getProvider(handle).createDirectory(URI.revive(resource))); + return Promise.resolve(this._getFsProvider(handle).createDirectory(URI.revive(resource))); } $watch(handle: number, session: number, resource: UriComponents, opts: files.IWatchOptions): void { - const subscription = this.getProvider(handle).watch(URI.revive(resource), opts); + const subscription = this._getFsProvider(handle).watch(URI.revive(resource), opts); this._watches.set(session, subscription); } @@ -272,7 +305,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } $open(handle: number, resource: UriComponents, opts: files.FileOpenOptions): Promise { - const provider = this.getProvider(handle); + const provider = this._getFsProvider(handle); if (!provider.open) { throw new Error('FileSystemProvider does not implement "open"'); } @@ -280,7 +313,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } $close(handle: number, fd: number): Promise { - const provider = this.getProvider(handle); + const provider = this._getFsProvider(handle); if (!provider.close) { throw new Error('FileSystemProvider does not implement "close"'); } @@ -288,7 +321,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } $read(handle: number, fd: number, pos: number, length: number): Promise { - const provider = this.getProvider(handle); + const provider = this._getFsProvider(handle); if (!provider.read) { throw new Error('FileSystemProvider does not implement "read"'); } @@ -299,14 +332,14 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } $write(handle: number, fd: number, pos: number, data: VSBuffer): Promise { - const provider = this.getProvider(handle); + const provider = this._getFsProvider(handle); if (!provider.write) { throw new Error('FileSystemProvider does not implement "write"'); } return Promise.resolve(provider.write(fd, pos, data.buffer, 0, data.byteLength)); } - private getProvider(handle: number): vscode.FileSystemProvider { + private _getFsProvider(handle: number): vscode.FileSystemProvider { const provider = this._fsProvider.get(handle); if (!provider) { const err = new Error(); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 661e98948c9..4aefb1cc5d6 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -133,7 +133,7 @@ class CodeLensAdapter { result.lenses.push({ cacheId: [cacheId, i], range: typeConvert.Range.from(lenses[i].range), - command: this._commands.toInternal2(lenses[i].command, disposables) + command: this._commands.toInternal(lenses[i].command, disposables) }); } @@ -167,7 +167,7 @@ class CodeLensAdapter { } newLens = newLens || lens; - symbol.command = this._commands.toInternal2(newLens.command || CodeLensAdapter._badCmd, disposables); + symbol.command = this._commands.toInternal(newLens.command || CodeLensAdapter._badCmd, disposables); return symbol; }); } @@ -368,7 +368,7 @@ class CodeActionAdapter { actions.push({ _isSynthetic: true, title: candidate.title, - command: this._commands.toInternal2(candidate, disposables), + command: this._commands.toInternal(candidate, disposables), }); } else { if (codeActionContext.only) { @@ -382,7 +382,7 @@ class CodeActionAdapter { // new school: convert code action actions.push({ title: candidate.title, - command: candidate.command && this._commands.toInternal2(candidate.command, disposables), + command: candidate.command && this._commands.toInternal(candidate.command, disposables), diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from), edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit), kind: candidate.kind && candidate.kind.value, @@ -735,7 +735,7 @@ class SuggestAdapter { i: item.keepWhitespace ? modes.CompletionItemInsertTextRule.KeepWhitespace : 0, k: item.commitCharacters, l: item.additionalTextEdits && item.additionalTextEdits.map(typeConvert.TextEdit.from), - m: this._commands.toInternal2(item.command, disposables), + m: this._commands.toInternal(item.command, disposables), }; // 'insertText'-logic diff --git a/src/vs/workbench/api/common/extHostMemento.ts b/src/vs/workbench/api/common/extHostMemento.ts index 3921d40d632..4502bacd671 100644 --- a/src/vs/workbench/api/common/extHostMemento.ts +++ b/src/vs/workbench/api/common/extHostMemento.ts @@ -38,7 +38,9 @@ export class ExtensionMemento implements IExtensionMemento { return this._init; } - get(key: string, defaultValue: T): T { + get(key: string): T | undefined; + get(key: string, defaultValue: T): T; + get(key: string, defaultValue?: T): T { let value = this._value[key]; if (typeof value === 'undefined') { value = defaultValue; @@ -46,11 +48,9 @@ export class ExtensionMemento implements IExtensionMemento { return value; } - update(key: string, value: any): Promise { + update(key: string, value: any): Promise { this._value[key] = value; - return this._storage - .setValue(this._shared, this._id, this._value) - .then(() => true); + return this._storage.setValue(this._shared, this._id, this._value); } dispose(): void { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 5ae57de0204..675201e622f 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -427,7 +427,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._acceptInputCommand = acceptInputCommand; - const internal = this._commands.converter.toInternal2(acceptInputCommand, this._acceptInputDisposables.value); + const internal = this._commands.converter.toInternal(acceptInputCommand, this._acceptInputDisposables.value); this._proxy.$updateSourceControl(this.handle, { acceptInputCommand: internal }); } @@ -447,7 +447,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._statusBarCommands = statusBarCommands; - const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal2(c, this._statusBarDisposables.value!)) as CommandDto[]; + const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c, this._statusBarDisposables.value!)) as CommandDto[]; this._proxy.$updateSourceControl(this.handle, { statusBarCommands: internal }); } diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index ac0c9878b0e..0524871953a 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -452,7 +452,7 @@ class ExtHostTreeView extends Disposable { description: extensionTreeItem.description, resourceUri: extensionTreeItem.resourceUri, tooltip: typeof extensionTreeItem.tooltip === 'string' ? extensionTreeItem.tooltip : undefined, - command: extensionTreeItem.command ? this.commands.toInternal2(extensionTreeItem.command, disposable) : undefined, + command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command, disposable) : undefined, contextValue: extensionTreeItem.contextValue, icon, iconDark: this.getDarkIconPath(extensionTreeItem) || icon, diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 9c2bcd503a5..c8ed21e278d 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2319,3 +2319,8 @@ export enum ExtensionExecutionContext { Local = 1, Remote = 2 } + +export enum ExtensionKind { + UI = 1, + Workspace = 2 +} diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index b6ad9c09ee2..98d6631c2a0 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -39,6 +39,10 @@ export class ExtHostWebview implements vscode.Webview { this._onMessageEmitter.dispose(); } + public get resourceRoot(): Promise { + return this._proxy.$getResourceRoot(this._handle); + } + public get html(): string { this.assertNotDisposed(); return this._html; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index e86d15a4751..3ad15b30f7d 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -125,10 +125,11 @@ export function createApiFactory( const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, new ExtHostOutputService(LogOutputChannelFactory, initData.logsLocation, rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); - if (initData.remoteAuthority) { + + if (initData.remote.isRemote && initData.remote.authority) { extHostTask.registerTaskSystem(Schemas.vscodeRemote, { scheme: Schemas.vscodeRemote, - authority: initData.remoteAuthority, + authority: initData.remote.authority, platform: process.platform }); @@ -150,7 +151,7 @@ export function createApiFactory( const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); // Register an output channel for exthost log - const outputChannelName = initData.remoteAuthority ? nls.localize('remote extension host Log', "Remote Extension Host") : nls.localize('extension host Log', "Extension Host"); + const outputChannelName = initData.remote.isRemote ? nls.localize('remote extension host Log', "Remote Extension Host") : nls.localize('extension host Log', "Extension Host"); extHostOutputService.createOutputChannelFromLogFile(outputChannelName, extHostLogService.logFile); // Register API-ish commands @@ -255,10 +256,22 @@ export function createApiFactory( return extHostClipboard; }, get shell() { + checkProposedApiEnabled(extension); return extHostTerminalService.getDefaultShell(configProvider); }, openExternal(uri: URI) { - return extHostWindow.openUri(uri, { allowTunneling: !!initData.remoteAuthority }); + return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote }); + }, + get remoteName() { + if (!initData.remote.authority) { + return undefined; + } + const pos = initData.remote.authority.indexOf('+'); + if (pos < 0) { + // funky? bad authority? + return initData.remote.authority; + } + return initData.remote.authority.substr(0, pos); } }; if (!initData.environment.extensionTestsLocationURI) { @@ -266,17 +279,21 @@ export function createApiFactory( Object.freeze(env); } + const extensionKind = initData.remote.isRemote + ? extHostTypes.ExtensionKind.Workspace + : extHostTypes.ExtensionKind.UI; + // namespace: extensions const extensions: typeof vscode.extensions = { getExtension(extensionId: string): Extension | undefined { const desc = extensionRegistry.getExtensionDescription(extensionId); if (desc) { - return new Extension(extensionService, desc); + return new Extension(extensionService, desc, extensionKind); } return undefined; }, get all(): Extension[] { - return extensionRegistry.getAllExtensionDescriptions().map((desc) => new Extension(extensionService, desc)); + return extensionRegistry.getAllExtensionDescriptions().map((desc) => new Extension(extensionService, desc, extensionKind)); }, get onDidChange() { return extensionRegistry.onDidChange; @@ -501,12 +518,13 @@ export function createApiFactory( createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel { return extHostWebviews.createWebviewPanel(extension, viewType, title, showOptions, options); }, - createWebviewTextEditorInset(editor: vscode.TextEditor, range: vscode.Range, options: vscode.WebviewOptions): vscode.WebviewEditorInset { + createWebviewTextEditorInset(editor: vscode.TextEditor, line: number, height: number, options: vscode.WebviewOptions): vscode.WebviewEditorInset { checkProposedApiEnabled(extension); - return extHostEditorInsets.createWebviewEditorInset(editor, range, options, extension); + return extHostEditorInsets.createWebviewEditorInset(editor, line, height, options, extension); }, createTerminal(nameOrOptions?: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { if (typeof nameOrOptions === 'object') { + nameOrOptions.hideFromUser = nameOrOptions.hideFromUser || (nameOrOptions.runInBackground && extension.enableProposedApi); return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); @@ -656,6 +674,10 @@ export function createApiFactory( registerFileSystemProvider(scheme, provider, options) { return extHostFileSystem.registerFileSystemProvider(scheme, provider, options); }, + get fs() { + checkProposedApiEnabled(extension); + return extHostFileSystem.fileSystem; + }, registerFileSearchProvider: proposedApiFunction(extension, (scheme: string, provider: vscode.FileSearchProvider) => { return extHostSearch.registerFileSearchProvider(scheme, provider); }), @@ -820,6 +842,7 @@ export function createApiFactory( EndOfLine: extHostTypes.EndOfLine, EventEmitter: Emitter, ExtensionExecutionContext: extHostTypes.ExtensionExecutionContext, + ExtensionKind: extHostTypes.ExtensionKind, CustomExecution: extHostTypes.CustomExecution, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, @@ -887,16 +910,18 @@ class Extension implements vscode.Extension { private _extensionService: ExtHostExtensionService; private _identifier: ExtensionIdentifier; - public id: string; - public extensionPath: string; - public packageJSON: IExtensionDescription; + readonly id: string; + readonly extensionPath: string; + readonly packageJSON: IExtensionDescription; + readonly extensionKind: vscode.ExtensionKind; - constructor(extensionService: ExtHostExtensionService, description: IExtensionDescription) { + constructor(extensionService: ExtHostExtensionService, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) { this._extensionService = extensionService; this._identifier = description.identifier; this.id = description.identifier.value; this.extensionPath = path.normalize(originalFSPath(description.extensionLocation)); this.packageJSON = description; + this.extensionKind = kind; } get isActive(): boolean { diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 5f4bd8be095..c5360322431 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -36,9 +36,15 @@ import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/work import { IURITransformer } from 'vs/base/common/uriIpc'; interface ITestRunner { + /** Old test runner API, as exported from `vscode/lib/testrunner` */ run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; } +interface INewTestRunner { + /** New test runner API, as explained in the extension test doc */ + run(): Promise; +} + export interface IHostUtils { exit(code?: number): void; exists(path: string): Promise; @@ -153,7 +159,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { const extensionPaths = await this.getExtensionPathIndex(); NodeModuleRequireInterceptor.INSTANCE.register(new VSCodeNodeModuleFactory(this._extensionApiFactory, extensionPaths, this._registry, configProvider)); NodeModuleRequireInterceptor.INSTANCE.register(new KeytarNodeModuleFactory(this._extHostContext.getProxy(MainContext.MainThreadKeytar), this._environment)); - if (this._initData.remoteAuthority) { + if (this._initData.remote.isRemote) { NodeModuleRequireInterceptor.INSTANCE.register(new OpenNodeModuleFactory( this._extHostContext.getProxy(MainContext.MainThreadWindow), this._extHostContext.getProxy(MainContext.MainThreadTelemetry), @@ -340,7 +346,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { }); } - private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { + private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); @@ -361,7 +367,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { globalStoragePath: this._storagePath.globalValue(extensionDescription), asAbsolutePath: (relativePath: string) => { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, logPath: that._extHostLogService.getLogDirectory(extensionDescription.identifier), - executionContext: this._initData.remoteAuthority ? ExtensionExecutionContext.Remote : ExtensionExecutionContext.Local + executionContext: this._initData.remote.isRemote ? ExtensionExecutionContext.Remote : ExtensionExecutionContext.Local, }); }); } @@ -525,7 +531,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { const extensionTestsPath = originalFSPath(extensionTestsLocationURI); // Require the test runner via node require from the provided path - let testRunner: ITestRunner | undefined; + let testRunner: ITestRunner | INewTestRunner | undefined; let requireError: Error | undefined; try { testRunner = require.__$__nodeRequire(extensionTestsPath); @@ -533,10 +539,10 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { requireError = error; } - // Execute the runner if it follows our spec + // Execute the runner if it follows the old `run` spec if (testRunner && typeof testRunner.run === 'function') { return new Promise((c, e) => { - testRunner!.run(extensionTestsPath, (error, failures) => { + const oldTestRunnerCallback = (error: Error, failures: number | undefined) => { if (error) { e(error.toString()); } else { @@ -545,7 +551,22 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { // after tests have run, we shutdown the host this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); - }); + }; + + const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback); + + // Using the new API `run(): Promise` + if (runResult && runResult.then) { + runResult + .then(() => { + c(); + this._gracefulExit(0); + }) + .catch((err: Error) => { + e(err.toString()); + this._gracefulExit(1); + }); + } }); } @@ -562,7 +583,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { // messages to the main process, we delay the exit() by some time setTimeout(() => { // If extension tests are running, give the exit code to the renderer - if (this._initData.remoteAuthority && !!this._initData.environment.extensionTestsLocationURI) { + if (this._initData.remote.isRemote && !!this._initData.environment.extensionTestsLocationURI) { this._mainThreadExtensionsProxy.$onExtensionHostExit(code); return; } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index f0dd94833f7..38fa69dc551 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -10,7 +10,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ILogService } from 'vs/platform/log/common/log'; import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -20,7 +20,8 @@ import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; +import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; +import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; const RENDERER_NO_PROCESS_ID = -1; @@ -115,9 +116,9 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi env?: { [key: string]: string | null }, waitOnExit?: boolean, strictEnv?: boolean, - runInBackground?: boolean + hideFromUser?: boolean ): void { - this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, runInBackground).then(terminal => { + this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser).then(terminal => { this._name = terminal.name; this._runQueuedRequests(terminal.id); }); @@ -312,7 +313,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options.name); - terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv, options.runInBackground); + terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv, options.hideFromUser); this._terminals.push(terminal); return terminal; } @@ -338,12 +339,22 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return terminalEnvironment.getDefaultShell( fetchSetting, this._isWorkspaceShellAllowed, - getDefaultShell(platform.platform), + getSystemShell(platform.platform), process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), process.env.windir ); } + private _getDefaultShellArgs(configProvider: ExtHostConfigProvider): string[] | string | undefined { + const fetchSetting = (key: string) => { + const setting = configProvider + .getConfiguration(key.substr(0, key.lastIndexOf('.'))) + .inspect(key.substr(key.lastIndexOf('.') + 1)); + return this._apiInspectConfigToPlain(setting); + }; + return terminalEnvironment.getDefaultShellArgs(fetchSetting, this._isWorkspaceShellAllowed); + } + public async resolveTerminalRenderer(id: number): Promise { // Check to see if the extension host already knows about this terminal. for (const terminalRenderer of this._terminalRenderers) { @@ -399,6 +410,11 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { }); } } + }); + } + + public $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void { + this._getTerminalByIdEventually(id).then(() => { // When a terminal's dimensions change, a renderer's _maximum_ dimensions change const renderer = this._getTerminalRendererById(id); if (renderer) { @@ -472,6 +488,12 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { }; } + private async _getNonInheritedEnv(): Promise { + const env = await getMainProcessParentEnv(); + env.VSCODE_IPC_HOOK_CLI = process.env['VSCODE_IPC_HOOK_CLI']!; + return env; + } + public async $createProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { const shellLaunchConfig: IShellLaunchConfig = { name: shellLaunchConfigDto.name, @@ -485,20 +507,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); const configProvider = await this._extHostConfiguration.getConfigProvider(); if (!shellLaunchConfig.executable) { - const fetchSetting = (key: string) => { - const setting = configProvider - .getConfiguration(key.substr(0, key.lastIndexOf('.'))) - .inspect(key.substr(key.lastIndexOf('.') + 1)); - return this._apiInspectConfigToPlain(setting); - }; - terminalEnvironment.mergeDefaultShellPathAndArgs( - shellLaunchConfig, - fetchSetting, - isWorkspaceShellAllowed || false, - getDefaultShell(platform.platform), - process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), - process.env.windir - ); + shellLaunchConfig.executable = this.getDefaultShell(configProvider); + shellLaunchConfig.args = this._getDefaultShellArgs(configProvider); } // Get the initial cwd @@ -519,6 +529,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { const envFromConfig = this._apiInspectConfigToPlain(configProvider.getConfiguration('terminal.integrated').inspect(`env.${platformKey}`)); const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2(); const variableResolver = workspaceFolders ? new ExtHostVariableResolverService(workspaceFolders, this._extHostDocumentsAndEditors, configProvider) : undefined; + const baseEnv = terminalConfig.get('inheritEnv', true) ? process.env as platform.IProcessEnvironment : await this._getNonInheritedEnv(); const env = terminalEnvironment.createTerminalEnvironment( shellLaunchConfig, lastActiveWorkspace, @@ -527,14 +538,15 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { isWorkspaceShellAllowed, pkg.version, terminalConfig.get('setLocaleVariables', false), - // Always inherit the environment as we need to be running in a login shell, this may - // change when macOS servers are supported - process.env as platform.IProcessEnvironment + baseEnv ); // Fork the process and listen for messages this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env); - const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, terminalConfig.get('windowsEnableConpty') as boolean, this._logService); + // TODO: Support conpty on remote, it doesn't seem to work for some reason? + // TODO: When conpty is enabled, only enable it when accessibilityMode is off + const enableConpty = false; //terminalConfig.get('windowsEnableConpty') as boolean; + const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, enableConpty, this._logService); p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd)); p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title)); p.onProcessData(data => this._proxy.$sendProcessData(id, data)); @@ -573,6 +585,18 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return id; } + public $requestAvailableShells(): Promise { + return detectAvailableShells(); + } + + public async $requestDefaultShellAndArgs(): Promise { + const configProvider = await this._extHostConfiguration.getConfigProvider(); + return Promise.resolve({ + shell: this.getDefaultShell(configProvider), + args: this._getDefaultShellArgs(configProvider) + }); + } + private _onProcessExit(id: number, exitCode: number): void { // Remove listeners this._terminalProcesses[id].dispose(); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts new file mode 100644 index 00000000000..60c6f23f234 --- /dev/null +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -0,0 +1,199 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import * as nls from 'vs/nls'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { domEvent } from 'vs/base/browser/event'; +import { Event } from 'vs/base/common/event'; +import { IDisposable, toDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $ } from 'vs/base/browser/dom'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { timeout } from 'vs/base/common/async'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; + +export class InspectContextKeysAction extends Action { + + static readonly ID = 'workbench.action.inspectContextKeys'; + static LABEL = nls.localize('inspect context keys', "Inspect Context Keys"); + + constructor( + id: string, + label: string, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IWindowService private readonly windowService: IWindowService, + ) { + super(id, label); + } + + run(): Promise { + const disposables = new DisposableStore(); + + const stylesheet = createStyleSheet(); + disposables.add(toDisposable(() => { + if (stylesheet.parentNode) { + stylesheet.parentNode.removeChild(stylesheet); + } + })); + createCSSRule('*', 'cursor: crosshair !important;', stylesheet); + + const hoverFeedback = document.createElement('div'); + document.body.appendChild(hoverFeedback); + disposables.add(toDisposable(() => document.body.removeChild(hoverFeedback))); + + hoverFeedback.style.position = 'absolute'; + hoverFeedback.style.pointerEvents = 'none'; + hoverFeedback.style.backgroundColor = 'rgba(255, 0, 0, 0.5)'; + hoverFeedback.style.zIndex = '1000'; + + const onMouseMove = domEvent(document.body, 'mousemove', true); + disposables.add(onMouseMove(e => { + const target = e.target as HTMLElement; + const position = getDomNodePagePosition(target); + + hoverFeedback.style.top = `${position.top}px`; + hoverFeedback.style.left = `${position.left}px`; + hoverFeedback.style.width = `${position.width}px`; + hoverFeedback.style.height = `${position.height}px`; + })); + + const onMouseDown = Event.once(domEvent(document.body, 'mousedown', true)); + onMouseDown(e => { e.preventDefault(); e.stopPropagation(); }, null, disposables); + + const onMouseUp = Event.once(domEvent(document.body, 'mouseup', true)); + onMouseUp(e => { + e.preventDefault(); + e.stopPropagation(); + + const context = this.contextKeyService.getContext(e.target as HTMLElement) as Context; + console.log(context.collectAllValues()); + this.windowService.openDevTools(); + + dispose(disposables); + }, null, disposables); + + return Promise.resolve(); + } +} + +export class ToggleScreencastModeAction extends Action { + + static readonly ID = 'workbench.action.toggleScreencastMode'; + static LABEL = nls.localize('toggle screencast mode', "Toggle Screencast Mode"); + + static disposable: IDisposable | undefined; + + constructor( + id: string, + label: string, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + ) { + super(id, label); + } + + async run(): Promise { + if (ToggleScreencastModeAction.disposable) { + ToggleScreencastModeAction.disposable.dispose(); + ToggleScreencastModeAction.disposable = undefined; + return; + } + + const container = this.layoutService.getWorkbenchElement(); + + const mouseMarker = append(container, $('div')); + mouseMarker.style.position = 'absolute'; + mouseMarker.style.border = '2px solid red'; + mouseMarker.style.borderRadius = '20px'; + mouseMarker.style.width = '20px'; + mouseMarker.style.height = '20px'; + mouseMarker.style.top = '0'; + mouseMarker.style.left = '0'; + mouseMarker.style.zIndex = '100000'; + mouseMarker.style.content = ' '; + mouseMarker.style.pointerEvents = 'none'; + mouseMarker.style.display = 'none'; + + const onMouseDown = domEvent(container, 'mousedown', true); + const onMouseUp = domEvent(container, 'mouseup', true); + const onMouseMove = domEvent(container, 'mousemove', true); + + const mouseListener = onMouseDown(e => { + mouseMarker.style.top = `${e.clientY - 10}px`; + mouseMarker.style.left = `${e.clientX - 10}px`; + mouseMarker.style.display = 'block'; + + const mouseMoveListener = onMouseMove(e => { + mouseMarker.style.top = `${e.clientY - 10}px`; + mouseMarker.style.left = `${e.clientX - 10}px`; + }); + + Event.once(onMouseUp)(() => { + mouseMarker.style.display = 'none'; + mouseMoveListener.dispose(); + }); + }); + + const keyboardMarker = append(container, $('div')); + keyboardMarker.style.position = 'absolute'; + keyboardMarker.style.backgroundColor = 'rgba(0, 0, 0 ,0.5)'; + keyboardMarker.style.width = '100%'; + keyboardMarker.style.height = '100px'; + keyboardMarker.style.bottom = '20%'; + keyboardMarker.style.left = '0'; + keyboardMarker.style.zIndex = '100000'; + keyboardMarker.style.pointerEvents = 'none'; + keyboardMarker.style.color = 'white'; + keyboardMarker.style.lineHeight = '100px'; + keyboardMarker.style.textAlign = 'center'; + keyboardMarker.style.fontSize = '56px'; + keyboardMarker.style.display = 'none'; + + const onKeyDown = domEvent(container, 'keydown', true); + let keyboardTimeout: IDisposable = Disposable.None; + + const keyboardListener = onKeyDown(e => { + keyboardTimeout.dispose(); + + const event = new StandardKeyboardEvent(e); + const keybinding = this.keybindingService.resolveKeyboardEvent(event); + const label = keybinding.getLabel(); + + if (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && this.keybindingService.mightProducePrintableCharacter(event) && label) { + keyboardMarker.textContent += ' ' + label; + } else { + keyboardMarker.textContent = label; + } + + keyboardMarker.style.display = 'block'; + + const promise = timeout(800); + keyboardTimeout = toDisposable(() => promise.cancel()); + + promise.then(() => { + keyboardMarker.textContent = ''; + keyboardMarker.style.display = 'none'; + }); + }); + + ToggleScreencastModeAction.disposable = toDisposable(() => { + mouseListener.dispose(); + keyboardListener.dispose(); + mouseMarker.remove(); + keyboardMarker.remove(); + }); + } +} + +const developerCategory = nls.localize('developer', "Developer"); +const registry = Registry.as(Extensions.WorkbenchActions); +registry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 9bffa5213e7..51962ccc1b8 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -18,7 +18,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; -import { isWindows, isLinux } from 'vs/base/common/platform'; +import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InEditorZenModeContext, IsCenteredLayoutContext } from 'vs/workbench/common/editor'; @@ -438,7 +438,7 @@ export class ToggleMenuBarAction extends Action { } } -if (isWindows || isLinux) { +if (isWindows || isLinux || isWeb) { registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMenuBarAction, ToggleMenuBarAction.ID, ToggleMenuBarAction.LABEL), 'View: Toggle Menu Bar', viewCategory); } diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts new file mode 100644 index 00000000000..cc2ad1776af --- /dev/null +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { IsFullscreenContext, IsDevelopmentContext } from 'vs/workbench/browser/contextkeys'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; + +export class ToggleFullScreenAction extends Action { + + static readonly ID = 'workbench.action.toggleFullScreen'; + static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); + + constructor( + id: string, + label: string, + @IWindowService private readonly windowService: IWindowService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService) { + super(id, label); + } + + run(): Promise { + const container = this.layoutService.getWorkbenchElement(); + + return this.windowService.toggleFullScreen(container); + } +} + +export class ReloadWindowAction extends Action { + + static readonly ID = 'workbench.action.reloadWindow'; + static LABEL = nls.localize('reloadWindow', "Reload Window"); + + constructor( + id: string, + label: string, + @IWindowService private readonly windowService: IWindowService + ) { + super(id, label); + } + + async run(): Promise { + await this.windowService.reloadWindow(); + + return true; + } +} + +const registry = Registry.as(Extensions.WorkbenchActions); + +const viewCategory = nls.localize('view', "View"); +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); + +const developerCategory = nls.localize('developer', "Developer"); +registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); + +KeybindingsRegistry.registerKeybindingRule({ + id: ReloadWindowAction.ID, + weight: KeybindingWeight.WorkbenchContrib + 50, + when: IsDevelopmentContext, + primary: KeyMod.CtrlCmd | KeyCode.KEY_R +}); + +// Appereance menu +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '1_toggle_view', + command: { + id: ToggleFullScreenAction.ID, + title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "&&Full Screen"), + toggled: IsFullscreenContext + }, + order: 1 +}); \ No newline at end of file diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 2c7298ebcdd..3cd1005e09d 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -11,12 +11,15 @@ import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/p import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { toResource } from 'vs/workbench/common/editor'; +import { URI } from 'vs/base/common/uri'; export class OpenFileAction extends Action { @@ -36,39 +39,33 @@ export class OpenFileAction extends Action { } } -export class OpenLocalFileAction extends Action { +export namespace OpenLocalFileCommand { + export const ID = 'workbench.action.files.openLocalFile'; + export const LABEL = nls.localize('openLocalFile', "Open Local File..."); - static readonly ID = 'workbench.action.files.openLocalFile'; - static LABEL = nls.localize('openLocalFile', "Open Local File..."); - - constructor( - id: string, - label: string, - @IFileDialogService private readonly dialogService: IFileDialogService - ) { - super(id, label); - } - - run(event?: any, data?: ITelemetryData): Promise { - return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data, availableFileSystems: [Schemas.file] }); + export function handler(): ICommandHandler { + return accessor => { + const dialogService = accessor.get(IFileDialogService); + return dialogService.pickFileAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); + }; } } -export class SaveLocalFileAction extends Action { +export namespace SaveLocalFileCommand { + export const ID = 'workbench.action.files.saveLocalFile'; + export const LABEL = nls.localize('saveLocalFile', "Save Local File..."); - static readonly ID = 'workbench.action.files.saveLocalFile'; - static LABEL = nls.localize('saveLocalFile', "Save Local File..."); - - constructor( - id: string, - label: string, - @IFileDialogService private readonly dialogService: IFileDialogService - ) { - super(id, label); - } - - run(event?: any, data?: ITelemetryData): Promise { - return this.dialogService.pickFileToSave({ availableFileSystems: [Schemas.file] }); + export function handler(): ICommandHandler { + return accessor => { + const textFileService = accessor.get(ITextFileService); + const editorService = accessor.get(IEditorService); + let resource: URI | undefined = toResource(editorService.activeEditor); + const options: ISaveOptions = { force: true, availableFileSystems: [Schemas.file] }; + if (resource) { + return textFileService.saveAs(resource, undefined, options); + } + return Promise.resolve(undefined); + }; } } @@ -90,21 +87,15 @@ export class OpenFolderAction extends Action { } } -export class OpenLocalFolderAction extends Action { +export namespace OpenLocalFolderCommand { + export const ID = 'workbench.action.files.openLocalFolder'; + export const LABEL = nls.localize('openLocalFolder', "Open Local Folder..."); - static readonly ID = 'workbench.action.files.openLocalFolder'; - static LABEL = nls.localize('openLocalFolder', "Open Local Folder..."); - - constructor( - id: string, - label: string, - @IFileDialogService private readonly dialogService: IFileDialogService - ) { - super(id, label); - } - - run(event?: any, data?: ITelemetryData): Promise { - return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data, availableFileSystems: [Schemas.file] }); + export function handler(): ICommandHandler { + return accessor => { + const dialogService = accessor.get(IFileDialogService); + return dialogService.pickFolderAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); + }; } } @@ -127,21 +118,16 @@ export class OpenFileFolderAction extends Action { } } -export class OpenLocalFileFolderAction extends Action { +export namespace OpenLocalFileFolderCommand { - static readonly ID = 'workbench.action.files.openLocalFileFolder'; - static LABEL = nls.localize('openLocalFileFolder', "Open Local..."); + export const ID = 'workbench.action.files.openLocalFileFolder'; + export const LABEL = nls.localize('openLocalFileFolder', "Open Local..."); - constructor( - id: string, - label: string, - @IFileDialogService private readonly dialogService: IFileDialogService - ) { - super(id, label); - } - - run(event?: any, data?: ITelemetryData): Promise { - return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data, availableFileSystems: [Schemas.file] }); + export function handler(): ICommandHandler { + return accessor => { + const dialogService = accessor.get(IFileDialogService); + return dialogService.pickFileFolderAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); + }; } } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 2054ceece3c..cce2dfe5632 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -20,7 +20,7 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { MIME_BINARY } from 'vs/base/common/mime'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; @@ -31,6 +31,7 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe import { IRecentFile } from 'vs/platform/history/common/history'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export interface IDraggedResource { resource: URI; @@ -327,8 +328,12 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: const lineDelimiter = isWindows ? '\r\n' : '\n'; event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(source.resource.fsPath)) : source.resource.toString()).join(lineDelimiter)); - // Download URL: enables support to drag a tab as file to desktop (only single file supported) - event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstSource.resource), firstSource.resource.toString()].join(':')); + const envService = accessor.get(IWorkbenchEnvironmentService); + if (!(isLinux && envService.configuration.remoteAuthority)) { + // Download URL: enables support to drag a tab as file to desktop (only single file supported) + // Not supported on linux remote due to chrome limitation https://github.com/microsoft/vscode-remote-release/issues/849 + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstSource.resource), firstSource.resource.toString()].join(':')); + } // Resource URLs: allows to drop multiple resources to a target in VS Code (not directories) const files = sources.filter(s => !s.isDirectory); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index e06870b2bb7..b378f17f8e6 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -9,7 +9,7 @@ import { EventType, addDisposableListener, addClass, removeClass, isAncestor, ge import { onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; -import { isWindows, isLinux, isMacintosh, isWeb } from 'vs/base/common/platform'; +import { isWindows, isLinux, isMacintosh, isWeb, isNative } from 'vs/base/common/platform'; import { pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; @@ -45,8 +45,6 @@ enum Settings { ZEN_MODE_RESTORE = 'zenMode.restore', - // TODO @misolori remove before shipping stable - ICON_EXPLORATION_ENABLED = 'workbench.iconExploration.enabled' } enum Storage { @@ -160,12 +158,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi wasSideBarVisible: false, wasPanelVisible: false, transitionDisposeables: new DisposableStore() - }, - - // TODO @misolori remove before shipping stable - iconExploration: { - enabled: false } + }; constructor( @@ -229,7 +223,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Menubar visibility changes - if ((isWindows || isLinux) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + if ((isWindows || isLinux || isWeb) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); } } @@ -299,11 +293,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const newMenubarVisibility = this.configurationService.getValue(Settings.MENUBAR_VISIBLE); this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); - // TODO @misolori remove before shipping stable - // Icon exploration on setting change - const newIconExplorationEnabled = this.configurationService.getValue(Settings.ICON_EXPLORATION_ENABLED); - this.setIconExploration(newIconExplorationEnabled); - } private setSideBarPosition(position: Position): void { @@ -417,10 +406,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Zen mode enablement this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); - // TODO @misolori remove before shipping stable - // Icon exploration - this.state.iconExploration.enabled = this.configurationService.getValue(Settings.ICON_EXPLORATION_ENABLED); - this.setIconExploration(this.state.iconExploration.enabled); } private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { @@ -535,7 +520,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; } else if (!this.state.fullscreen) { return true; - } else if (isMacintosh) { + } else if (isMacintosh && isNative) { return false; } else if (this.state.menuBar.visibility === 'visible') { return true; @@ -689,19 +674,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - // TODO @misolori remove before shipping stable - private setIconExploration(enabled: boolean): void { - this.state.iconExploration.enabled = enabled; - - // Update DOM - if (enabled) { - document.body.dataset.exploration = 'icon-exploration'; - } else { - document.body.dataset.exploration = ''; - } - - } - protected createWorkbenchLayout(instantiationService: IInstantiationService): void { const titleBar = this.getPart(Parts.TITLEBAR_PART); const editorPart = this.getPart(Parts.EDITOR_PART); diff --git a/src/vs/workbench/browser/media/icons.css b/src/vs/workbench/browser/media/icons.css deleted file mode 100644 index 1d000db29a0..00000000000 --- a/src/vs/workbench/browser/media/icons.css +++ /dev/null @@ -1,1262 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/**************** - Colors -****************/ -:root { - --blue: #00539c; - --gray: #424242; - --grayLight: #848484; - --green: #388a34; - --greenLight: #9cce9c; - --orange: #c27d1a; - --orangeLight: #ff8e00; - --purple: #652d90; - --red: #a31515; - --redLight: #e51400; - --yellow: #fc0; -} - -:root .vs-dark { - --blue: #75beff; - --gray: #c5c5c5; - --grayLight: #848484; - --green: #89d185; - --greenLight: #9cce9c; - --orange: #e8ab53; - --orangeLight: #ff8e00; - --purple: #b180d7; - --red: #f48771; - --redLight: #e51400; - --yellow: #fc0; -} - -:root .hc-black { - --blue: #75beff; - --gray: #FFF; - --grayLight: #FFF; - --green: #89d185; - --greenLight: #9cce9c; - --orange: #e8ab53; - --orangeLight: #ff8e00; - --purple: #b180d7; - --red: #f48771; - --redLight: #e51400; - --yellow: #fc0; -} - - -/**************** - Base -****************/ -body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header[aria-label="Open Editors Section"] > .actions .action-label.icon, -body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.explorer-action.icon, -body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.toolbar-toggle-more.icon, -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label^="Explorer"] .icon, -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label="Search actions"] .icon, -body[data-exploration^="icon-exploration"] .monaco-findInput > .controls .monaco-custom-checkbox::before, -body[data-exploration^="icon-exploration"] .monaco-workbench .search-view .query-details .file-types .controls > .monaco-custom-checkbox.useExcludesAndIgnoreFiles::before, -body[data-exploration^="icon-exploration"] .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude::before, -body[data-exploration^="icon-exploration"] .search-view .search-widget .replace-container .monaco-action-bar .action-item .icon, -body[data-exploration^="icon-exploration"] .search-view a[class^="action-"], -body[data-exploration^="icon-exploration"] .monaco-workbench .search-view .query-details .more, -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label="Source Control: Git actions"] .icon[data-title="git.commit"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label="Source Control: Git actions"] .icon[data-title="git.refresh"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label="Debug actions"] .icon, -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label^="Extensions"] .icon, -body[data-exploration^="icon-exploration"] .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label[data-title^="git."], -body[data-exploration^="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label, -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .content > .debug-viewlet .actions .action-label.icon, -body[data-exploration^="icon-exploration"] .monaco-workbench .debug-toolbar .drag-area, -body[data-exploration^="icon-exploration"] .monaco-workbench .debug-toolbar .action-label, -body[data-exploration^="icon-exploration"] .debug-breakpoint, -body[data-exploration^="icon-exploration"] .debug-viewlet .debug-breakpoints .breakpoint > .icon, -body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-item .action-label.extension-action.manage, -body[data-exploration^="icon-exploration"] .extension-ratings > .star, -body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon, -body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon, -body[data-exploration^="icon-exploration"] .monaco-toolbar .action-label.toolbar-toggle-more, -body[data-exploration^="icon-exploration"] .monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label, -body[data-exploration^="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon::before, -body[data-exploration^="icon-exploration"] .monaco-workbench .symbol-icon::before, -body[data-exploration^="icon-exploration"] .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close, -body[data-exploration^="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .readMore, -body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .icon.error, -body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .icon.warning, -body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item-icon, -body[data-exploration^="icon-exploration"] .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action, -body[data-exploration^="icon-exploration"] .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action, -body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action, -body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action, -body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action, -body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-label, -body[data-exploration^="icon-exploration"] .monaco-workbench .activitybar > .content .monaco-action-bar .action-label[title="References"], -body[data-exploration^="icon-exploration"] .markers-panel .marker-icon, -body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix, -body[data-exploration^="icon-exploration"] .monaco-tl-twistie.collapsible:not(.loading), -body[data-exploration^="icon-exploration"] .file-icon-themable-tree .monaco-tree-row.has-children .content::before, -body[data-exploration^="icon-exploration"] .file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before, -body[data-exploration^="icon-exploration"] .monaco-breadcrumbs .monaco-breadcrumb-item:not(:nth-child(2))::before, -body[data-exploration^="icon-exploration"] .monaco-tl-twistie.collapsible.collapsed:not(.loading), -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action, -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .close-editor-action, -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.closeActiveEditor"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.splitEditor"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="git.openChange"], -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace-all::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .previous::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .next::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .close-fw::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .expand::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .collapse::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .monaco-checkbox .label::before, -body[data-exploration^="icon-exploration"] .monaco-workbench .quick-open-sidebyside-vertical, -body[data-exploration^="icon-exploration"] .monaco-tree-action.collapse-all, -body[data-exploration^="icon-exploration"] .monaco-editor .debug-breakpoint, -body[data-exploration^="icon-exploration"] .monaco-editor .debug-focused-stack-frame, -body[data-exploration^="icon-exploration"] .monaco-editor .debug-top-stack-frame, -body[data-exploration^="icon-exploration"] .monaco-editor .debug-breakpoint-hint, -body[data-exploration^="icon-exploration"] .monaco-editor .debug-breakpoint-conditional, -body[data-exploration^="icon-exploration"] .monaco-editor .debug-breakpoint-log, -body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .severity-icon, -body[data-exploration^="icon-exploration"] .monaco-editor .margin-view-overlays .folding, -body[data-exploration^="icon-exploration"] .monaco-workbench .explorer-action.save-all, -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="git.init"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="_workbench.openUserSettingsEditor"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.compareEditor.previousChange"], -body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.icon[data-title="git.commit"], -body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.icon[data-title="git.refresh"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="references-view.refresh"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="references-view.clear"], -body[data-exploration^="icon-exploration"] .customview-tree .monaco-tree .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions .action-label[data-title="references-view.remove"], -body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-up, -body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-down, -body[data-exploration^="icon-exploration"] .monaco-workbench .explorer-viewlet .action-close-all-files, -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group, -body[data-exploration^="icon-exploration"] .monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more, -body[data-exploration^="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .clear-input, -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.openGlobalKeybindingsFile"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="settings.switchToJSON"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="markdown.showSource"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label.file-icon[title^="Preview"]::before, -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="markdown.showPreviewToSide"], -body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label, -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.compareEditor.nextChange"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.compareEditor.previousChange"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.compareEditor.nextChange"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="git.openFile"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="git.openFile"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.openGlobalKeybindings"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="toggle.diff.ignoreTrimWhitespace"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="toggle.diff.ignoreTrimWhitespace"], -body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions .action-label.icon[data-title="peekview.close"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.splitEditor"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="git.openChange"], -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover, -body[data-exploration^="icon-exploration"] .monaco-workbench .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action, -body[data-exploration^="icon-exploration"] .monaco-workbench .explorer-viewlet .explorer-open-editors .close-editor-action { - background-image: none !important; - background: var(--gray); - -webkit-mask-repeat: no-repeat; - -webkit-mask-position: center; - -webkit-mask-size: 16px; -} - -/* Center via Flexbox + Search expand */ -body[data-exploration^="icon-exploration"] .search-view .search-widget .toggle-replace-button.collapse, -body[data-exploration^="icon-exploration"] .search-view .search-widget .toggle-replace-button.expand, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace-all, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .previous, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .next, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .close-fw, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .expand, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .collapse, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .monaco-checkbox .label { - display: flex; - align-items: center; - justify-content: center; - background-image: none !important; -} - -/* Before elements */ -body[data-exploration="icon-exploration"] .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox.checked::before, -body[data-exploration^="icon-exploration"] .search-view .search-widget .toggle-replace-button.collapse::before, -body[data-exploration^="icon-exploration"] .search-view .search-widget .toggle-replace-button.expand::before, -body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace-all::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .previous::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .next::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .close-fw::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .expand::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .collapse::before, -body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .monaco-checkbox .label::before, -body[data-exploration^="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .monaco-custom-checkbox::before { - content: ""; - width: 16px; - height: 16px; - background-color: var(--gray); - -webkit-mask-repeat: no-repeat; - -webkit-mask-position: center; - -webkit-mask-size: 16px; -} - -/* For icons that are a custom checkbox and use focus */ -body[data-exploration="icon-exploration"] .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox.checked, -body[data-exploration^="icon-exploration"] .monaco-findInput > .controls .monaco-custom-checkbox, -body[data-exploration^="icon-exploration"] .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude, -body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header, -body[data-exploration^="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .monaco-custom-checkbox { - background-image: none !important; -} - -body[data-exploration="icon-exploration"] .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox.checked::before, -body[data-exploration^="icon-exploration"] .monaco-findInput > .controls .monaco-custom-checkbox::before, -body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-up::before, -body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-down::before, -body[data-exploration^="icon-exploration"] .monaco-workbench .search-view .query-details .file-types .controls > .monaco-custom-checkbox.useExcludesAndIgnoreFiles::before, -body[data-exploration^="icon-exploration"] .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude::before { - content: ""; - width: 16px; - height: 16px; - display: block; -} - -/******************** - ACTIVITY BAR -********************/ - -body[data-exploration^="icon-exploration"] .monaco-workbench .activitybar > .content .monaco-action-bar .badge .badge-content { - font-size: 9px; - font-weight: 600; - height: 16px; - line-height: 16px; - padding: 0 4px; -} - -body[data-exploration^="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label { - -webkit-mask-size: 24px !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.explore { - -webkit-mask-image: url("images/activitybar/files-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.search { - -webkit-mask-image: url("images/activitybar/search-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.scm { - -webkit-mask-image: url("images/activitybar/git-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.debug { - -webkit-mask-image: url("images/activitybar/debug-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.extensions { - -webkit-mask-image: url("images/activitybar/extensions-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.update-activity { - -webkit-mask-image: url("images/activitybar/settings-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-label.toggle-more { - -webkit-mask-image: url("images/activitybar/more-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .activitybar > .content .monaco-action-bar .action-label[title="References"] { - -webkit-mask-image: url("images/activitybar/references-alt1.svg"); -} - - -/**************** - Explorer -****************/ - -body[data-exploration="icon-exploration"] .monaco-workbench .flip-editor-layout { - -webkit-mask-image: url("images/explorer/layout-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.save-all, -body[data-exploration="icon-exploration"] .monaco-workbench .save-all { - -webkit-mask-image: url("images/explorer/save-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .explorer-viewlet .action-close-all-files { - -webkit-mask-image: url("images/explorer/close-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.new-file { - -webkit-mask-image: url("images/explorer/add-file-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.new-folder { - -webkit-mask-image: url("images/explorer/add-folder-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.refresh-explorer, -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="references-view.refresh"] { - -webkit-mask-image: url("images/explorer/refresh-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.collapse-explorer { - -webkit-mask-image: url("images/explorer/collapse-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-toolbar .action-label.toolbar-toggle-more { - -webkit-mask-image: url("images/explorer/more-alt1.svg"); -} - - -/**************** - Search -****************/ - -body[data-exploration="icon-exploration"] .monaco-workbench .search-action.refresh { - -webkit-mask-image: url("images/search/refresh-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .search-action.clear-search-results, -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="references-view.clear"] { - -webkit-mask-image: url("images/search/clear-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .search-action.collapse { - -webkit-mask-image: url("images/search/collapse-all-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .search-action.cancel-search { - -webkit-mask-image: url("images/search/stop-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .search-view .search-widget .replace-container .monaco-action-bar .action-item .icon { - -webkit-mask-image: url("images/search/replace-all-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .search-view .query-details .file-types .controls > .monaco-custom-checkbox.useExcludesAndIgnoreFiles::before, -body[data-exploration="icon-exploration"] .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude::before { - -webkit-mask-image: url("images/search/exclude-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-custom-checkbox.monaco-case-sensitive::before { - -webkit-mask-image: url("images/search/case-sensitive-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-custom-checkbox.monaco-whole-word::before { - -webkit-mask-image: url("images/search/whole-word-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-custom-checkbox.monaco-regex::before { - -webkit-mask-image: url("images/search/regex-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .search-view .action-replace { - -webkit-mask-image: url("images/search/replace-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .search-view .action-replace-all { - -webkit-mask-image: url("images/search/replace-all-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .search-view .action-remove { - -webkit-mask-image: url("images/search/remove-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .search-view .query-details .more { - -webkit-mask-image: url("images/search/more-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .search-view .search-widget .toggle-replace-button.collapse::before { - -webkit-mask-image: url("images/search/collapse-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .search-view .search-widget .toggle-replace-button.expand::before { - -webkit-mask-image: url("images/search/expand-alt1.svg"); -} - - -/**************** - Git -****************/ - -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="git.init"] { - -webkit-mask-image: url("images/git/initialze.svg"); -} - -body[data-exploration="icon-exploration"] .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox.checked::before, -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="git.commit"], -body[data-exploration="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.icon[data-title="git.commit"] { - -webkit-mask-image: url("images/git/check-alt1.svg"); -} -/* Refresh */ -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="git.refresh"], -body[data-exploration="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.icon[data-title="git.refresh"] { - -webkit-mask-image: url("images/git/refresh-alt1.svg"); -} -/* Stage */ -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="git.stageChange"], -body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label[data-title="git.stageAll"], -body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.stage"] { - -webkit-mask-image: url("images/git/stage-alt1.svg"); -} -/* Unstage */ -body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label[data-title="git.unstageAll"], -body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.unstage"] { - -webkit-mask-image: url("images/git/unstage-alt1.svg"); -} -/* Discard */ -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="git.revertChange"], -body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label[data-title="git.cleanAll"], -body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.clean"] { - -webkit-mask-image: url("images/git/clean-alt1.svg"); -} -/* Open File */ -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="_workbench.openUserSettingsEditor"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="git.openFile"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="git.openFile"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.openGlobalKeybindings"], -body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.openFile2"] { - -webkit-mask-image: url("images/git/gotofile-alt1.svg"); -} -/* Chevrons */ -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.compareEditor.nextChange"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.compareEditor.nextChange"], -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="editor.action.marker.next"], -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="editor.action.dirtydiff.next"] { - -webkit-mask-image: url("images/git/next-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.compareEditor.previousChange"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.compareEditor.previousChange"], -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="editor.action.marker.prev"], -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="editor.action.dirtydiff.previous"] { - -webkit-mask-image: url("images/git/previous-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="toggle.diff.ignoreTrimWhitespace"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="toggle.diff.ignoreTrimWhitespace"]{ - -webkit-mask-image: url("images/git/whitespace-alt1.svg"); -} - - -/**************** - Debug -****************/ - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-action.start, -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.continue"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon { - -webkit-mask-image: url("images/debug/start-alt1.svg"); - background: var(--green) !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-action.configure { - -webkit-mask-image: url("images/debug/gear-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-action.toggle-repl { - -webkit-mask-image: url("images/debug/repl-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .debug-viewlet .debug-action.add-watch-expression, -body[data-exploration="icon-exploration"] .debug-viewlet .debug-action.add-function-breakpoint { - -webkit-mask-image: url("images/debug/add-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .debug-viewlet .debug-action.remove-all { - -webkit-mask-image: url("images/debug/close-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .debug-viewlet .debug-action.breakpoints-activate { - -webkit-mask-image: url("images/debug/breakpoint-activate-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .drag-area { - -webkit-mask-image: url("images/debug/drag-alt1.svg"); - background: var(--grayLight); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stepOver"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stepOver"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stepBack"], -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stepBack"] { - -webkit-mask-image: url("images/debug/step-over-alt1.svg"); - background: var(--blue) !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stepInto"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stepInto"] { - -webkit-mask-image: url("images/debug/step-into-alt1.svg"); - background: var(--blue) !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stepOut"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stepOut"] { - -webkit-mask-image: url("images/debug/step-out-alt1.svg"); - background: var(--blue) !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.continue"], -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.rever"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.continue"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.rever"] { - -webkit-mask-image: url("images/debug/continue-alt1.svg"); - background: var(--blue) !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.restart"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.restart"] { - -webkit-mask-image: url("images/debug/restart-alt1.svg"); - background: var(--green) !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.pause"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.pause"] { - -webkit-mask-image: url("images/debug/pause-alt1.svg"); - background: var(--green) !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stop"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stop"] { - -webkit-mask-image: url("images/debug/stop-alt1.svg"); - background: var(--red) !important; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.disconnect"], -body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.disconnect"] { - -webkit-mask-image: url("images/debug/disconnect-alt1.svg"); - background: var(--red) !important; -} - -body[data-exploration="icon-exploration"] .debug-breakpoint, -body[data-exploration="icon-exploration"] .debug-breakpoint-hint, -body[data-exploration="icon-exploration"] .debug-breakpoint.icon, -body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column::before { - -webkit-mask-image: url("images/debug/breakpoint-alt1.svg"); - background: var(--redLight) !important; -} - -body[data-exploration="icon-exploration"] .debug-breakpoint-hint:not(.debug-breakpoint):not(.debug-breakpoint-conditional):not(.debug-top-stack-frame):not(.debug-focused-stack-frame):not(.debug-breakpoint-log) { - opacity: .5 !important; -} - -body[data-exploration="icon-exploration"] .debug-breakpoint-disabled.icon { - -webkit-mask-image: url("images/debug/breakpoint-unverified-alt1.svg"); - background: var(--grayLight); -} - -body[data-exploration="icon-exploration"] .debug-breakpoint-unverified, -body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column.debug-breakpoint-disabled-column::before { - -webkit-mask-image: url("images/debug/breakpoint-alt1.svg"); - background: var(--grayLight); -} - -body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame-column::before, -body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame { - -webkit-mask-image: url("images/debug/current-arrow-alt1.svg"); - background: var(--yellow) !important; -} - -body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame.debug-breakpoint, -body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame.debug-breakpoint-conditional, -body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame.debug-breakpoint-log, -body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column.debug-breakpoint-column.debug-top-stack-frame-column::before { - -webkit-mask-image: url("images/debug/current-and-breakpoint-alt1.svg"); - background: var(--yellow) !important; -} - -body[data-exploration="icon-exploration"] .monaco-editor .debug-focused-stack-frame.debug-breakpoint, -body[data-exploration="icon-exploration"] .monaco-editor .debug-focused-stack-frame.debug-breakpoint-conditional, -body[data-exploration="icon-exploration"] .monaco-editor .debug-focused-stack-frame.debug-breakpoint-log { - -webkit-mask-image: url("images/debug/stackframe-and-breakpoint-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .debug-breakpoint-log, -body[data-exploration="icon-exploration"] .debug-breakpoint-log.icon, -body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column.debug-breakpoint-log-column::before { - -webkit-mask-image: url("images/debug/breakpoint-log-alt1.svg"); - background: var(--redLight) !important; -} - -body[data-exploration="icon-exploration"] .debug-breakpoint-log-disabled, -body[data-exploration="icon-exploration"] .debug-breakpoint-log-disabled.icon, -body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-log-disabled-column::before { - -webkit-mask-image: url("images/debug/breakpoint-log-alt1.svg"); - background: var(--grayLight); -} - -body[data-exploration="icon-exploration"] .debug-breakpoint-log-unverified, -body[data-exploration="icon-exploration"] .debug-breakpoint-log-unverified.icon, -body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-log-unverified-column::before { - -webkit-mask-image: url("images/debug/breakpoint-log-unverified-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .debug-breakpoint-conditional, -body[data-exploration="icon-exploration"] .debug-breakpoint-conditional.icon, -body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column.debug-breakpoint-conditional-column::before { - -webkit-mask-image: url("images/debug/breakpoint-conditional-alt1.svg"); - background: var(--redLight) !important; -} - -body[data-exploration="icon-exploration"] .monaco-editor .debug-focused-stack-frame { - -webkit-mask-image: url("images/debug/stackframe-arrow-alt1.svg"); - background: var(--green) !important; -} - -body[data-exploration="icon-exploration"] .debug-function-breakpoint, -body[data-exploration="icon-exploration"] .debug-function-breakpoint.icon { - -webkit-mask-image: url("images/debug/breakpoint-function-alt1.svg"); - background: var(--redLight) !important; -} - -body[data-exploration="icon-exploration"] .debug-function-breakpoint-unverified { - -webkit-mask-image: url("images/debug/breakpoint-function-unverified-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .debug-function-breakpoint-disabled { - -webkit-mask-image: url("images/debug/breakpoint-function-disabled-alt1.svg"); -} - -/**************** - Extensions -****************/ - -body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header { - align-items: center; -} - -body[data-exploration="icon-exploration"] .monaco-action-bar .action-item .action-label.clear-extensions { - -webkit-mask-image: url("images/extensions/clear-alt1.svg"); -} - -body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty), -body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings { - display: flex; - align-items: center; - justify-content: center; -} - -body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon::before, -body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon::before { - content: "" !important; -} - -body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon, -body[data-exploration^="icon-exploration"] .extension-ratings.small > .full { - width: 12px; - height: 12px; - -webkit-mask-size: 12px; -} - -body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon { - width: 16px; - height: 16px; - -webkit-mask-size: 16px; -} - -body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle > span:not(:first-child):not(:empty) { - position: relative; -} - -body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle > .install > .count { - margin-left: 22px; -} - -body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon { - position: absolute; - left: 14px; - top: 0; -} - -body[data-exploration="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon, -body[data-exploration="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon { - -webkit-mask-image: url("images/extensions/download-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .extension-ratings > .full { - -webkit-mask-image: url("images/extensions/star-full-alt1.svg"); - background: var(--orangeLight); -} - -body[data-exploration="icon-exploration"] .extension-ratings > .half { - -webkit-mask-image: url("images/extensions/star-half-alt1.svg"); - background: var(--orangeLight); -} - -body[data-exploration="icon-exploration"] .extension-ratings > .empty { - -webkit-mask-image: url("images/extensions/star-empty-alt1.svg"); - background: var(--gray); -} - -body[data-exploration="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-item .action-label.extension-action.manage { - -webkit-mask-image: url("images/extensions/gear-alt1.svg"); -} - - -/**************** - Panels -****************/ - -body[data-exploration="icon-exploration"] .monaco-workbench .hide-panel-action { - -webkit-mask-image: url("images/panel/close-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .maximize-panel-action { - -webkit-mask-image: url("images/panel/up-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .minimize-panel-action { - -webkit-mask-image: url("images/panel/down-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-tree-action.collapse-all { - -webkit-mask-image: url("images/panel/collapse-all-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .output-action.open-log-file { - -webkit-mask-image: url("images/panel/gotofile-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .output-action.open-log-file { - -webkit-mask-image: url("images/panel/gotofile-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .output-action.output-scroll-unlock { - -webkit-mask-image: url("images/panel/output-unlock-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .output-action.output-scroll-lock { - -webkit-mask-image: url("images/panel/output-lock-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .output-action.clear-output { - -webkit-mask-image: url("images/panel/clear-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .debug-action.clear-repl { - -webkit-mask-image: url("images/panel/clear-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .terminal-action.kill { - -webkit-mask-image: url("images/panel/kill-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .terminal-action.split, -body[data-exploration="icon-exploration"] .monaco-workbench .quick-open-sidebyside-vertical { - -webkit-mask-image: url("images/panel/split-horizontal-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .panel.right .terminal-action.split { - -webkit-mask-image: url("images/panel/split-vertical-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .terminal-action.new { - -webkit-mask-image: url("images/panel/add-alt1.svg"); -} - - -/**************** - IntelliSense -****************/ - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close { - -webkit-mask-image: url("images/intellisense/close-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .readMore { - -webkit-mask-image: url("images/intellisense/info-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon::before { - background-image: none !important; -} -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.method::before, -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.function::before, -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constructor::before { - -webkit-mask-image: url("images/intellisense/method-alt1.svg"); - background-color: var(--purple); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.field::before { - -webkit-mask-image: url("images/intellisense/field-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.event::before { - -webkit-mask-image: url("images/intellisense/event-alt1.svg"); - background-color: var(--orange); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.operator::before { - -webkit-mask-image: url("images/intellisense/operator-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.variable::before { - -webkit-mask-image: url("images/intellisense/variable-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.class::before { - -webkit-mask-image: url("images/intellisense/class-alt1.svg"); - background-color: var(--orange); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.interface::before { - -webkit-mask-image: url("images/intellisense/interface-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.struct::before { - -webkit-mask-image: url("images/intellisense/structure-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.type-parameter::before { - -webkit-mask-image: url("images/intellisense/parameter-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.module::before { - -webkit-mask-image: url("images/intellisense/namespace-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.property::before { - -webkit-mask-image: url("images/intellisense/property-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.unit::before { - -webkit-mask-image: url("images/intellisense/ruler-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constant::before { - -webkit-mask-image: url("images/intellisense/constant-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.value::before, -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum::before { - -webkit-mask-image: url("images/intellisense/enumerator-alt1.svg"); - background-color: var(--orange); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum-member::before { - -webkit-mask-image: url("images/intellisense/enum-member-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.keyword::before { - -webkit-mask-image: url("images/intellisense/keyword-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.text::before { - -webkit-mask-image: url("images/intellisense/string-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.color::before { - -webkit-mask-image: url("images/intellisense/color-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.file::before { - -webkit-mask-image: url("images/intellisense/file-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.reference::before { - -webkit-mask-image: url("images/intellisense/reference-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.snippet::before { - -webkit-mask-image: url("images/intellisense/snippet-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.folder::before { - -webkit-mask-image: url("images/intellisense/folder-alt1.svg"); -} - -/**************** - Breadcrumbs -****************/ - -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { - -webkit-mask-image: none; - background: transparent; -} - -body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .symbol-icon { - margin-left: 4px; -} - -body[data-exploration^="icon-exploration"] .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon::before { - left: 0; - top: -3px; -} - -body[data-exploration^="icon-exploration"] .monaco-workbench .breadcrumbs-control .symbol-icon::before, -body[data-exploration^="icon-exploration"] .monaco-workbench .monaco-breadcrumb-item .symbol-icon { - top: -2px; -} - -body[data-exploration^="icon-exploration"] .monaco-workbench .symbol-icon { - position: relative; - background-image: none !important; - -webkit-mask-position: left center; -} - -body[data-exploration^="icon-exploration"] .monaco-workbench .symbol-icon::before { - content: ""; - position: absolute; - left: -3px; - width: 16px; - height: 22px; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon::before { - -webkit-mask-image: url("images/intellisense/field-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.constant::before { - -webkit-mask-image: url("images/intellisense/constant-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.enum::before { - -webkit-mask-image: url("images/intellisense/enumerator-alt1.svg"); - background-color: var(--orange); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.enum-member::before { - -webkit-mask-image: url("images/intellisense/enum-member-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.struct::before { - -webkit-mask-image: url("images/intellisense/structure-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.event::before { - -webkit-mask-image: url("images/intellisense/event-alt1.svg"); - background-color: var(--orange); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.operator::before { - -webkit-mask-image: url("images/intellisense/operator-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.type-parameter::before { - -webkit-mask-image: url("images/intellisense/parameter-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.boolean::before, -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.null::before { - -webkit-mask-image: url("images/intellisense/boolean-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.class::before { - -webkit-mask-image: url("images/intellisense/class-alt1.svg"); - background-color: var(--orange); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.constructor::before, -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.method::before, -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.function::before { - -webkit-mask-image: url("images/intellisense/method-alt1.svg"); - background-color: var(--purple); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.file::before { - -webkit-mask-image: url("images/intellisense/file-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.field::before { - -webkit-mask-image: url("images/intellisense/field-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.variable::before { - -webkit-mask-image: url("images/intellisense/variable-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.array::before { - -webkit-mask-image: url("images/intellisense/array-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.keyword::before { - -webkit-mask-image: url("images/intellisense/keyword-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.interface::before { - -webkit-mask-image: url("images/intellisense/interface-alt1.svg"); - background-color: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.object::before, -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.namespace::before, -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.package::before, -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.module::before { - -webkit-mask-image: url("images/intellisense/namespace-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.number::before { - -webkit-mask-image: url("images/intellisense/numeric-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.property::before { - -webkit-mask-image: url("images/intellisense/property-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.string::before { - -webkit-mask-image: url("images/intellisense/key-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.key::before { - -webkit-mask-image: url("images/intellisense/key-alt1.svg"); -} - -body[data-exploration^="icon-exploration"] .monaco-editor .lightbulb-glyph { - background-image: none !important; - -webkit-mask-repeat: no-repeat; - -webkit-mask-position: center center; - -webkit-mask-size: 16px; -} - -body[data-exploration="icon-exploration"] .monaco-editor .lightbulb-glyph, -body[data-exploration="icon-exploration"] .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { - -webkit-mask-image: url("images/intellisense/lightbulb-alt1.svg"); - background-color: var(--yellow); -} - -body[data-exploration="icon-exploration"] .monaco-editor .lightbulb-glyph.autofixable, -body[data-exploration="icon-exploration"] .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix.autofixable { - -webkit-mask-image: url("images/intellisense/lightbulb-autofix-alt1.svg"); - background-color: var(--blue); -} - - -/**************** - Notifications -****************/ - -body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info, -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .severity-icon.severity-info, -body[data-exploration="icon-exploration"] .markers-panel .marker-icon.severity-info { - -webkit-mask-image: url("images/notifications/info-alt1.svg"); - background: var(--blue); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning, -body[data-exploration="icon-exploration"] .markers-panel .marker-icon.severity-warning, -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .severity-icon.severity-warning { - -webkit-mask-image: url("images/notifications/warning-alt1.svg"); - background: var(--yellow); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error, -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .severity-icon.severity-error, -body[data-exploration="icon-exploration"] .markers-panel .marker-icon.severity-error { - -webkit-mask-image: url("images/notifications/error-alt1.svg"); - background: var(--redLight); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action { - -webkit-mask-image: url("images/notifications/close-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action { - -webkit-mask-image: url("images/notifications/up-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action { - -webkit-mask-image: url("images/notifications/down-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .configure-notification-action { - -webkit-mask-image: url("images/notifications/configure-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action { - -webkit-mask-image: url("images/notifications/closeall-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action { - -webkit-mask-image: url("images/notifications/down-alt1.svg"); -} - -body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .marker-icon { - min-width: 16px; -} - - -/**************** - Tree -****************/ - -body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header::before { - position: absolute; - left: 2px; - top: 2px; -} - -body[data-exploration="icon-exploration"] .file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before, -body[data-exploration="icon-exploration"] .monaco-panel-view .panel > .panel-header.expanded::before, -body[data-exploration="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-up, -body[data-exploration="icon-exploration"] .monaco-tl-twistie.collapsible:not(.loading) { - -webkit-mask-image: url("images/tree/expand-alt1.svg"); - background-image: url("images/tree/expand-empty.svg") !important; -} - -body[data-exploration="icon-exploration"] .file-icon-themable-tree .monaco-tree-row.has-children .content::before, -body[data-exploration="icon-exploration"] .monaco-breadcrumbs .monaco-breadcrumb-item:not(:nth-child(2))::before, -body[data-exploration="icon-exploration"] .monaco-panel-view .panel > .panel-header::before, -body[data-exploration="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-down, -body[data-exploration="icon-exploration"] .monaco-tl-twistie.collapsible.collapsed:not(.loading) { - -webkit-mask-image: url("images/tree/collapse-alt1.svg"); - background-image: url("images/tree/expand-empty.svg") !important; - -} - -body[data-exploration="icon-exploration"] .customview-tree .monaco-tree .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions .action-label[data-title="references-view.remove"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.closeActiveEditor"], -body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions .action-label.icon[data-title="peekview.close"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .close-editor-action, -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover, -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group, -body[data-exploration="icon-exploration"] .monaco-workbench .explorer-viewlet .explorer-open-editors .close-editor-action { - -webkit-mask-image: url("images/tree/close-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action, -body[data-exploration="icon-exploration"] .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action { - -webkit-mask-image: url("images/tree/dirty-alt1.svg"); -} - - -/**************** - Tree -****************/ - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.splitEditor"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.splitEditor"] { - -webkit-mask-image: url("images/editor/split-horizontal-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[title="Split Editor Down"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[title="Split Editor Down"] { - -webkit-mask-image: url("images/editor/split-vertical-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.openChange"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="git.openChange"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="git.openChange"] { - -webkit-mask-image: url("images/editor/open-change-alt1.svg"); -} - - -/**************** - Find -****************/ - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .previous::before { - -webkit-mask-image: url("images/find/previous-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .next::before { - -webkit-mask-image: url("images/find/next-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .close-fw::before { - -webkit-mask-image: url("images/find/close-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .monaco-checkbox .label::before { - -webkit-mask-image: url("images/find/selection-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .replace::before { - -webkit-mask-image: url("images/find/replace-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .replace-all::before { - -webkit-mask-image: url("images/find/replace-all-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .expand::before { - -webkit-mask-image: url("images/find/expand-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .collapse::before { - -webkit-mask-image: url("images/find/collapse-alt1.svg"); -} - -/**************** - Misc -****************/ - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="markdown.showPreviewToSide"]{ - -webkit-mask-image: url("images/misc/preview-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label.file-icon[title^="Preview"]::before{ - -webkit-mask-image: url("images/misc/preview-icon-alt1.svg"); - -webkit-mask-position: left center; -} - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="markdown.showSource"]{ - -webkit-mask-image: url("images/misc/gotofile-alt1.svg") -} - -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.openGlobalKeybindingsFile"], -body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="settings.switchToJSON"]{ - -webkit-mask-image: url("images/misc/json-alt1.svg") -} - -body[data-exploration^="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .monaco-custom-checkbox::before { - display: block; -} - -body[data-exploration="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .record-keys::before { - -webkit-mask-image: url("images/misc/keyboard-alt1.svg") -} - -body[data-exploration="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .clear-input { - -webkit-mask-image: url("images/misc/clear-alt1.svg") -} - -body[data-exploration="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .sort-by-precedence::before { - -webkit-mask-image: url("images/misc/precedence-alt1.svg") -} - -body[data-exploration="icon-exploration"] .monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more { - -webkit-mask-image: url("images/misc/more-alt1.svg"); - background: var(--gray) !important; -} - -body[data-exploration^="icon-exploration"] .monaco-editor .margin-view-overlays .folding, -body[data-exploration^="icon-exploration"] .monaco-editor .margin-view-overlays .folding.collapsed { - -webkit-mask-position: 75% 50%; -} - -body[data-exploration="icon-exploration"] .monaco-editor .margin-view-overlays .folding { - -webkit-mask-image: url("images/misc/fold-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .margin-view-overlays .folding.collapsed { - -webkit-mask-image: url("images/misc/unfold-alt1.svg"); -} - -body[data-exploration="icon-exploration"] .monaco-editor .find-widget .matchesCount { - margin-right: 0; - padding-right: 0; -} - -body[data-exploration="icon-exploration"] .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox.checked::before { - -webkit-mask-size: 14px; -} \ No newline at end of file diff --git a/src/vs/workbench/browser/media/images/activitybar/debug-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/debug-alt1.svg deleted file mode 100644 index dfe350a283f..00000000000 --- a/src/vs/workbench/browser/media/images/activitybar/debug-alt1.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/activitybar/extensions-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/extensions-alt1.svg deleted file mode 100644 index 3fd6c5b9ca1..00000000000 --- a/src/vs/workbench/browser/media/images/activitybar/extensions-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/activitybar/files-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/files-alt1.svg deleted file mode 100644 index f2ddfd97b79..00000000000 --- a/src/vs/workbench/browser/media/images/activitybar/files-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/activitybar/git-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/git-alt1.svg deleted file mode 100644 index 6ce8a7c1c51..00000000000 --- a/src/vs/workbench/browser/media/images/activitybar/git-alt1.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/activitybar/more-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/more-alt1.svg deleted file mode 100644 index 6729ca3c90d..00000000000 --- a/src/vs/workbench/browser/media/images/activitybar/more-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/activitybar/references-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/references-alt1.svg deleted file mode 100644 index 4cd6e61dfb1..00000000000 --- a/src/vs/workbench/browser/media/images/activitybar/references-alt1.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/activitybar/search-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/search-alt1.svg deleted file mode 100644 index fb1a6e7bd59..00000000000 --- a/src/vs/workbench/browser/media/images/activitybar/search-alt1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/activitybar/settings-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/settings-alt1.svg deleted file mode 100644 index b2a61102112..00000000000 --- a/src/vs/workbench/browser/media/images/activitybar/settings-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/add-alt1.svg b/src/vs/workbench/browser/media/images/debug/add-alt1.svg deleted file mode 100644 index fb50c6c2849..00000000000 --- a/src/vs/workbench/browser/media/images/debug/add-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-activate-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-activate-alt1.svg deleted file mode 100644 index 3e6b0a4c4df..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-activate-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-alt1.svg deleted file mode 100644 index e391c3b0852..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-conditional-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-conditional-alt1.svg deleted file mode 100644 index 9794d6b5522..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-conditional-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-function-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-function-alt1.svg deleted file mode 100644 index 76d1f05da2c..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-function-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-function-disabled-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-function-disabled-alt1.svg deleted file mode 100644 index 76d1f05da2c..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-function-disabled-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-function-unverified-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-function-unverified-alt1.svg deleted file mode 100644 index ece98d0f356..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-function-unverified-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-log-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-log-alt1.svg deleted file mode 100644 index 21ada7b2928..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-log-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-log-unverified-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-log-unverified-alt1.svg deleted file mode 100644 index f540bf94efb..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-log-unverified-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-unverified-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-unverified-alt1.svg deleted file mode 100644 index 6bfa8fd2f5a..00000000000 --- a/src/vs/workbench/browser/media/images/debug/breakpoint-unverified-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/close-alt1.svg b/src/vs/workbench/browser/media/images/debug/close-alt1.svg deleted file mode 100644 index 8af24a5b1e8..00000000000 --- a/src/vs/workbench/browser/media/images/debug/close-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/debug/continue-alt1.svg b/src/vs/workbench/browser/media/images/debug/continue-alt1.svg deleted file mode 100644 index d9e1bcfa25d..00000000000 --- a/src/vs/workbench/browser/media/images/debug/continue-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/debug/current-and-breakpoint-alt1.svg b/src/vs/workbench/browser/media/images/debug/current-and-breakpoint-alt1.svg deleted file mode 100644 index df3c2501a53..00000000000 --- a/src/vs/workbench/browser/media/images/debug/current-and-breakpoint-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/debug/current-arrow-alt1.svg b/src/vs/workbench/browser/media/images/debug/current-arrow-alt1.svg deleted file mode 100644 index f62a9fb6db4..00000000000 --- a/src/vs/workbench/browser/media/images/debug/current-arrow-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/disconnect-alt1.svg b/src/vs/workbench/browser/media/images/debug/disconnect-alt1.svg deleted file mode 100644 index 71aae0dd887..00000000000 --- a/src/vs/workbench/browser/media/images/debug/disconnect-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/drag-alt1.svg b/src/vs/workbench/browser/media/images/debug/drag-alt1.svg deleted file mode 100644 index b6b93f31fdf..00000000000 --- a/src/vs/workbench/browser/media/images/debug/drag-alt1.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/debug/gear-alt1.svg b/src/vs/workbench/browser/media/images/debug/gear-alt1.svg deleted file mode 100644 index 7db1664af78..00000000000 --- a/src/vs/workbench/browser/media/images/debug/gear-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/debug/pause-alt1.svg b/src/vs/workbench/browser/media/images/debug/pause-alt1.svg deleted file mode 100644 index 1050e04f2d9..00000000000 --- a/src/vs/workbench/browser/media/images/debug/pause-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/repl-alt1.svg b/src/vs/workbench/browser/media/images/debug/repl-alt1.svg deleted file mode 100644 index f13e57b89ac..00000000000 --- a/src/vs/workbench/browser/media/images/debug/repl-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/debug/restart-alt1.svg b/src/vs/workbench/browser/media/images/debug/restart-alt1.svg deleted file mode 100644 index 28c63aae4d9..00000000000 --- a/src/vs/workbench/browser/media/images/debug/restart-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/debug/stackframe-and-breakpoint-alt1.svg b/src/vs/workbench/browser/media/images/debug/stackframe-and-breakpoint-alt1.svg deleted file mode 100644 index df3c2501a53..00000000000 --- a/src/vs/workbench/browser/media/images/debug/stackframe-and-breakpoint-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/debug/stackframe-arrow-alt1.svg b/src/vs/workbench/browser/media/images/debug/stackframe-arrow-alt1.svg deleted file mode 100644 index f62a9fb6db4..00000000000 --- a/src/vs/workbench/browser/media/images/debug/stackframe-arrow-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/start-alt1.svg b/src/vs/workbench/browser/media/images/debug/start-alt1.svg deleted file mode 100644 index 30196ede168..00000000000 --- a/src/vs/workbench/browser/media/images/debug/start-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/debug/step-into-alt1.svg b/src/vs/workbench/browser/media/images/debug/step-into-alt1.svg deleted file mode 100644 index 2113d7b1987..00000000000 --- a/src/vs/workbench/browser/media/images/debug/step-into-alt1.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/debug/step-out-alt1.svg b/src/vs/workbench/browser/media/images/debug/step-out-alt1.svg deleted file mode 100644 index 6724964aea1..00000000000 --- a/src/vs/workbench/browser/media/images/debug/step-out-alt1.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/debug/step-over-alt1.svg b/src/vs/workbench/browser/media/images/debug/step-over-alt1.svg deleted file mode 100644 index f2454843ae5..00000000000 --- a/src/vs/workbench/browser/media/images/debug/step-over-alt1.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/debug/stop-alt1.svg b/src/vs/workbench/browser/media/images/debug/stop-alt1.svg deleted file mode 100644 index 4e348228b61..00000000000 --- a/src/vs/workbench/browser/media/images/debug/stop-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/editor/open-change-alt1.svg b/src/vs/workbench/browser/media/images/editor/open-change-alt1.svg deleted file mode 100644 index 0c8297f940b..00000000000 --- a/src/vs/workbench/browser/media/images/editor/open-change-alt1.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/editor/split-horizontal-alt1.svg b/src/vs/workbench/browser/media/images/editor/split-horizontal-alt1.svg deleted file mode 100644 index c92025ddd92..00000000000 --- a/src/vs/workbench/browser/media/images/editor/split-horizontal-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/editor/split-vertical-alt1.svg b/src/vs/workbench/browser/media/images/editor/split-vertical-alt1.svg deleted file mode 100644 index f407c08fa34..00000000000 --- a/src/vs/workbench/browser/media/images/editor/split-vertical-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/explorer/add-file-alt1.svg b/src/vs/workbench/browser/media/images/explorer/add-file-alt1.svg deleted file mode 100644 index 138351f66b2..00000000000 --- a/src/vs/workbench/browser/media/images/explorer/add-file-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/explorer/add-folder-alt1.svg b/src/vs/workbench/browser/media/images/explorer/add-folder-alt1.svg deleted file mode 100644 index 0f1a2bfb9ba..00000000000 --- a/src/vs/workbench/browser/media/images/explorer/add-folder-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/explorer/close-alt1.svg b/src/vs/workbench/browser/media/images/explorer/close-alt1.svg deleted file mode 100644 index 8af24a5b1e8..00000000000 --- a/src/vs/workbench/browser/media/images/explorer/close-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/explorer/collapse-alt1.svg b/src/vs/workbench/browser/media/images/explorer/collapse-alt1.svg deleted file mode 100644 index f2e0e5dd5f6..00000000000 --- a/src/vs/workbench/browser/media/images/explorer/collapse-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/explorer/layout-alt1.svg b/src/vs/workbench/browser/media/images/explorer/layout-alt1.svg deleted file mode 100644 index 40c1b46b197..00000000000 --- a/src/vs/workbench/browser/media/images/explorer/layout-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/explorer/more-alt1.svg b/src/vs/workbench/browser/media/images/explorer/more-alt1.svg deleted file mode 100644 index 3d7068f6b4c..00000000000 --- a/src/vs/workbench/browser/media/images/explorer/more-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/explorer/refresh-alt1.svg b/src/vs/workbench/browser/media/images/explorer/refresh-alt1.svg deleted file mode 100644 index a940b8ef4a6..00000000000 --- a/src/vs/workbench/browser/media/images/explorer/refresh-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/explorer/save-alt1.svg b/src/vs/workbench/browser/media/images/explorer/save-alt1.svg deleted file mode 100644 index 5756795cb42..00000000000 --- a/src/vs/workbench/browser/media/images/explorer/save-alt1.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/browser/media/images/extensions/clear-alt1.svg b/src/vs/workbench/browser/media/images/extensions/clear-alt1.svg deleted file mode 100644 index 63be0fae215..00000000000 --- a/src/vs/workbench/browser/media/images/extensions/clear-alt1.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/media/images/extensions/download-alt1.svg b/src/vs/workbench/browser/media/images/extensions/download-alt1.svg deleted file mode 100644 index 211864d2c8b..00000000000 --- a/src/vs/workbench/browser/media/images/extensions/download-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/extensions/gear-alt1.svg b/src/vs/workbench/browser/media/images/extensions/gear-alt1.svg deleted file mode 100644 index 7db1664af78..00000000000 --- a/src/vs/workbench/browser/media/images/extensions/gear-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/extensions/star-empty-alt1.svg b/src/vs/workbench/browser/media/images/extensions/star-empty-alt1.svg deleted file mode 100644 index a35ded971f7..00000000000 --- a/src/vs/workbench/browser/media/images/extensions/star-empty-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/extensions/star-full-alt1.svg b/src/vs/workbench/browser/media/images/extensions/star-full-alt1.svg deleted file mode 100644 index 2413e6ecb1f..00000000000 --- a/src/vs/workbench/browser/media/images/extensions/star-full-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/extensions/star-half-alt1.svg b/src/vs/workbench/browser/media/images/extensions/star-half-alt1.svg deleted file mode 100644 index 4e8dcd71f3b..00000000000 --- a/src/vs/workbench/browser/media/images/extensions/star-half-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/find/close-alt1.svg b/src/vs/workbench/browser/media/images/find/close-alt1.svg deleted file mode 100644 index 2512e9d61aa..00000000000 --- a/src/vs/workbench/browser/media/images/find/close-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/find/collapse-alt1.svg b/src/vs/workbench/browser/media/images/find/collapse-alt1.svg deleted file mode 100644 index 6d01adc07ea..00000000000 --- a/src/vs/workbench/browser/media/images/find/collapse-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/find/expand-alt1.svg b/src/vs/workbench/browser/media/images/find/expand-alt1.svg deleted file mode 100644 index a1a96a60926..00000000000 --- a/src/vs/workbench/browser/media/images/find/expand-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/find/next-alt1.svg b/src/vs/workbench/browser/media/images/find/next-alt1.svg deleted file mode 100644 index d2660e6e90f..00000000000 --- a/src/vs/workbench/browser/media/images/find/next-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/find/previous-alt1.svg b/src/vs/workbench/browser/media/images/find/previous-alt1.svg deleted file mode 100644 index 6f8d0cbcb6d..00000000000 --- a/src/vs/workbench/browser/media/images/find/previous-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/find/replace-all-alt1.svg b/src/vs/workbench/browser/media/images/find/replace-all-alt1.svg deleted file mode 100644 index 66437cf4410..00000000000 --- a/src/vs/workbench/browser/media/images/find/replace-all-alt1.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/find/replace-alt1.svg b/src/vs/workbench/browser/media/images/find/replace-alt1.svg deleted file mode 100644 index b599b6e5432..00000000000 --- a/src/vs/workbench/browser/media/images/find/replace-alt1.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/browser/media/images/find/selection-alt1.svg b/src/vs/workbench/browser/media/images/find/selection-alt1.svg deleted file mode 100644 index e0503a93a51..00000000000 --- a/src/vs/workbench/browser/media/images/find/selection-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/git/check-alt1.svg b/src/vs/workbench/browser/media/images/git/check-alt1.svg deleted file mode 100644 index a5597d7c404..00000000000 --- a/src/vs/workbench/browser/media/images/git/check-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/git/clean-alt1.svg b/src/vs/workbench/browser/media/images/git/clean-alt1.svg deleted file mode 100644 index 2f3dd525c0d..00000000000 --- a/src/vs/workbench/browser/media/images/git/clean-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/git/close-alt1.svg b/src/vs/workbench/browser/media/images/git/close-alt1.svg deleted file mode 100644 index 64618b61760..00000000000 --- a/src/vs/workbench/browser/media/images/git/close-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/git/gotofile-alt1.svg b/src/vs/workbench/browser/media/images/git/gotofile-alt1.svg deleted file mode 100644 index aabfc34c3e4..00000000000 --- a/src/vs/workbench/browser/media/images/git/gotofile-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/git/initialze.svg b/src/vs/workbench/browser/media/images/git/initialze.svg deleted file mode 100644 index fb50c6c2849..00000000000 --- a/src/vs/workbench/browser/media/images/git/initialze.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/git/next-alt1.svg b/src/vs/workbench/browser/media/images/git/next-alt1.svg deleted file mode 100644 index d2660e6e90f..00000000000 --- a/src/vs/workbench/browser/media/images/git/next-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/git/previous-alt1.svg b/src/vs/workbench/browser/media/images/git/previous-alt1.svg deleted file mode 100644 index 6f8d0cbcb6d..00000000000 --- a/src/vs/workbench/browser/media/images/git/previous-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/git/refresh-alt1.svg b/src/vs/workbench/browser/media/images/git/refresh-alt1.svg deleted file mode 100644 index a940b8ef4a6..00000000000 --- a/src/vs/workbench/browser/media/images/git/refresh-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/git/stage-alt1.svg b/src/vs/workbench/browser/media/images/git/stage-alt1.svg deleted file mode 100644 index fb50c6c2849..00000000000 --- a/src/vs/workbench/browser/media/images/git/stage-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/git/unstage-alt1.svg b/src/vs/workbench/browser/media/images/git/unstage-alt1.svg deleted file mode 100644 index ae942eb6748..00000000000 --- a/src/vs/workbench/browser/media/images/git/unstage-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/git/whitespace-alt1.svg b/src/vs/workbench/browser/media/images/git/whitespace-alt1.svg deleted file mode 100644 index b17a38ab079..00000000000 --- a/src/vs/workbench/browser/media/images/git/whitespace-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/array-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/array-alt1.svg deleted file mode 100644 index b058a7ca9cc..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/array-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/boolean-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/boolean-alt1.svg deleted file mode 100644 index 8fbcf89f2fd..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/boolean-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/class-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/class-alt1.svg deleted file mode 100644 index c939df47de5..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/class-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/close-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/close-alt1.svg deleted file mode 100644 index 1d8840d9276..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/close-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/color-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/color-alt1.svg deleted file mode 100644 index 914bb6f48d5..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/color-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/constant-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/constant-alt1.svg deleted file mode 100644 index bbb9e6dcbf8..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/constant-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/enum-member-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/enum-member-alt1.svg deleted file mode 100644 index e182868e71e..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/enum-member-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/enumerator-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/enumerator-alt1.svg deleted file mode 100644 index 7b87fce3202..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/enumerator-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/event-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/event-alt1.svg deleted file mode 100644 index 9c7c2504c24..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/event-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/field-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/field-alt1.svg deleted file mode 100644 index e547fb51a2d..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/field-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/file-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/file-alt1.svg deleted file mode 100644 index 9a0f5212ac7..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/file-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/folder-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/folder-alt1.svg deleted file mode 100644 index 8d3f68206e0..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/folder-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/info-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/info-alt1.svg deleted file mode 100644 index ef9fe8777a3..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/info-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/interface-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/interface-alt1.svg deleted file mode 100644 index 9114ce3f3e8..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/interface-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/key-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/key-alt1.svg deleted file mode 100644 index 58c6cf4ca11..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/key-alt1.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/keyword-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/keyword-alt1.svg deleted file mode 100644 index f21364efec0..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/keyword-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/lightbulb-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/lightbulb-alt1.svg deleted file mode 100644 index 1583a31632c..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/lightbulb-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/lightbulb-autofix-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/lightbulb-autofix-alt1.svg deleted file mode 100644 index d5ef68f9b83..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/lightbulb-autofix-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/method-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/method-alt1.svg deleted file mode 100644 index 392febfaa6b..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/method-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/namespace-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/namespace-alt1.svg deleted file mode 100644 index bb4c103c752..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/namespace-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/numeric-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/numeric-alt1.svg deleted file mode 100644 index 90cf05e747a..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/numeric-alt1.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/operator-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/operator-alt1.svg deleted file mode 100644 index 20f50387a69..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/operator-alt1.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/parameter-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/parameter-alt1.svg deleted file mode 100644 index 00198f67f4e..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/parameter-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/property-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/property-alt1.svg deleted file mode 100644 index 828113202a9..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/property-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/reference-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/reference-alt1.svg deleted file mode 100644 index aabfc34c3e4..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/reference-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/ruler-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/ruler-alt1.svg deleted file mode 100644 index eec16f41fbf..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/ruler-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/snippet-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/snippet-alt1.svg deleted file mode 100644 index ee82045f2ac..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/snippet-alt1.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/string-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/string-alt1.svg deleted file mode 100644 index 1aa09c086cb..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/string-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/structure-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/structure-alt1.svg deleted file mode 100644 index f0e857ed7ca..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/structure-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/intellisense/variable-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/variable-alt1.svg deleted file mode 100644 index 8cf0dbb7116..00000000000 --- a/src/vs/workbench/browser/media/images/intellisense/variable-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/misc/clear-alt1.svg b/src/vs/workbench/browser/media/images/misc/clear-alt1.svg deleted file mode 100644 index 0e624a23191..00000000000 --- a/src/vs/workbench/browser/media/images/misc/clear-alt1.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/media/images/misc/fold-alt1.svg b/src/vs/workbench/browser/media/images/misc/fold-alt1.svg deleted file mode 100644 index 69efb3e6957..00000000000 --- a/src/vs/workbench/browser/media/images/misc/fold-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/misc/gotofile-alt1.svg b/src/vs/workbench/browser/media/images/misc/gotofile-alt1.svg deleted file mode 100644 index aabfc34c3e4..00000000000 --- a/src/vs/workbench/browser/media/images/misc/gotofile-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/misc/json-alt1.svg b/src/vs/workbench/browser/media/images/misc/json-alt1.svg deleted file mode 100644 index 9e8f99141fc..00000000000 --- a/src/vs/workbench/browser/media/images/misc/json-alt1.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/misc/keyboard-alt1.svg b/src/vs/workbench/browser/media/images/misc/keyboard-alt1.svg deleted file mode 100644 index 6181acae7ef..00000000000 --- a/src/vs/workbench/browser/media/images/misc/keyboard-alt1.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/misc/more-alt1.svg b/src/vs/workbench/browser/media/images/misc/more-alt1.svg deleted file mode 100644 index 3d7068f6b4c..00000000000 --- a/src/vs/workbench/browser/media/images/misc/more-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/misc/precedence-alt1.svg b/src/vs/workbench/browser/media/images/misc/precedence-alt1.svg deleted file mode 100644 index 2609f1f40f9..00000000000 --- a/src/vs/workbench/browser/media/images/misc/precedence-alt1.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/media/images/misc/preview-alt1.svg b/src/vs/workbench/browser/media/images/misc/preview-alt1.svg deleted file mode 100644 index 25bf0b58bb9..00000000000 --- a/src/vs/workbench/browser/media/images/misc/preview-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/misc/preview-icon-alt1.svg b/src/vs/workbench/browser/media/images/misc/preview-icon-alt1.svg deleted file mode 100644 index df907d5dd14..00000000000 --- a/src/vs/workbench/browser/media/images/misc/preview-icon-alt1.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/media/images/misc/split-alt1.svg b/src/vs/workbench/browser/media/images/misc/split-alt1.svg deleted file mode 100644 index c92025ddd92..00000000000 --- a/src/vs/workbench/browser/media/images/misc/split-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/misc/unfold-alt1.svg b/src/vs/workbench/browser/media/images/misc/unfold-alt1.svg deleted file mode 100644 index bb57558f608..00000000000 --- a/src/vs/workbench/browser/media/images/misc/unfold-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/notifications/close-alt1.svg b/src/vs/workbench/browser/media/images/notifications/close-alt1.svg deleted file mode 100644 index 64618b61760..00000000000 --- a/src/vs/workbench/browser/media/images/notifications/close-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/notifications/closeall-alt1.svg b/src/vs/workbench/browser/media/images/notifications/closeall-alt1.svg deleted file mode 100644 index 72bf0a8b54a..00000000000 --- a/src/vs/workbench/browser/media/images/notifications/closeall-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/notifications/configure-alt1.svg b/src/vs/workbench/browser/media/images/notifications/configure-alt1.svg deleted file mode 100644 index 7db1664af78..00000000000 --- a/src/vs/workbench/browser/media/images/notifications/configure-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/notifications/down-alt1.svg b/src/vs/workbench/browser/media/images/notifications/down-alt1.svg deleted file mode 100644 index 7042b08fddf..00000000000 --- a/src/vs/workbench/browser/media/images/notifications/down-alt1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/notifications/error-alt1.svg b/src/vs/workbench/browser/media/images/notifications/error-alt1.svg deleted file mode 100644 index e39bf5e0e4a..00000000000 --- a/src/vs/workbench/browser/media/images/notifications/error-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/notifications/info-alt1.svg b/src/vs/workbench/browser/media/images/notifications/info-alt1.svg deleted file mode 100644 index 4a597c7b804..00000000000 --- a/src/vs/workbench/browser/media/images/notifications/info-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/notifications/up-alt1.svg b/src/vs/workbench/browser/media/images/notifications/up-alt1.svg deleted file mode 100644 index d5edcc4c305..00000000000 --- a/src/vs/workbench/browser/media/images/notifications/up-alt1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/notifications/warning-alt1.svg b/src/vs/workbench/browser/media/images/notifications/warning-alt1.svg deleted file mode 100644 index 948c0b4decf..00000000000 --- a/src/vs/workbench/browser/media/images/notifications/warning-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/panel/add-alt1.svg b/src/vs/workbench/browser/media/images/panel/add-alt1.svg deleted file mode 100644 index fb50c6c2849..00000000000 --- a/src/vs/workbench/browser/media/images/panel/add-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/panel/clear-alt1.svg b/src/vs/workbench/browser/media/images/panel/clear-alt1.svg deleted file mode 100644 index 63be0fae215..00000000000 --- a/src/vs/workbench/browser/media/images/panel/clear-alt1.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/media/images/panel/close-all-alt1.svg b/src/vs/workbench/browser/media/images/panel/close-all-alt1.svg deleted file mode 100644 index 8af24a5b1e8..00000000000 --- a/src/vs/workbench/browser/media/images/panel/close-all-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/panel/close-alt1.svg b/src/vs/workbench/browser/media/images/panel/close-alt1.svg deleted file mode 100644 index 3818f7d5350..00000000000 --- a/src/vs/workbench/browser/media/images/panel/close-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/panel/collapse-all-alt1.svg b/src/vs/workbench/browser/media/images/panel/collapse-all-alt1.svg deleted file mode 100644 index f2e0e5dd5f6..00000000000 --- a/src/vs/workbench/browser/media/images/panel/collapse-all-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/panel/down-alt1.svg b/src/vs/workbench/browser/media/images/panel/down-alt1.svg deleted file mode 100644 index de3313624a4..00000000000 --- a/src/vs/workbench/browser/media/images/panel/down-alt1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/panel/gear-alt1.svg b/src/vs/workbench/browser/media/images/panel/gear-alt1.svg deleted file mode 100644 index 7db1664af78..00000000000 --- a/src/vs/workbench/browser/media/images/panel/gear-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/panel/gotofile-alt1.svg b/src/vs/workbench/browser/media/images/panel/gotofile-alt1.svg deleted file mode 100644 index aabfc34c3e4..00000000000 --- a/src/vs/workbench/browser/media/images/panel/gotofile-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/panel/kill-alt1.svg b/src/vs/workbench/browser/media/images/panel/kill-alt1.svg deleted file mode 100644 index bc448290732..00000000000 --- a/src/vs/workbench/browser/media/images/panel/kill-alt1.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/panel/output-lock-alt1.svg b/src/vs/workbench/browser/media/images/panel/output-lock-alt1.svg deleted file mode 100644 index 70d268965a0..00000000000 --- a/src/vs/workbench/browser/media/images/panel/output-lock-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/panel/output-unlock-alt1.svg b/src/vs/workbench/browser/media/images/panel/output-unlock-alt1.svg deleted file mode 100644 index cf8268d37d2..00000000000 --- a/src/vs/workbench/browser/media/images/panel/output-unlock-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/panel/split-horizontal-alt1.svg b/src/vs/workbench/browser/media/images/panel/split-horizontal-alt1.svg deleted file mode 100644 index c92025ddd92..00000000000 --- a/src/vs/workbench/browser/media/images/panel/split-horizontal-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/panel/split-vertical-alt1.svg b/src/vs/workbench/browser/media/images/panel/split-vertical-alt1.svg deleted file mode 100644 index f407c08fa34..00000000000 --- a/src/vs/workbench/browser/media/images/panel/split-vertical-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/panel/up-alt1.svg b/src/vs/workbench/browser/media/images/panel/up-alt1.svg deleted file mode 100644 index e7ac370945f..00000000000 --- a/src/vs/workbench/browser/media/images/panel/up-alt1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/search/case-sensitive-alt1.svg b/src/vs/workbench/browser/media/images/search/case-sensitive-alt1.svg deleted file mode 100644 index 418172b6866..00000000000 --- a/src/vs/workbench/browser/media/images/search/case-sensitive-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/search/clear-alt1.svg b/src/vs/workbench/browser/media/images/search/clear-alt1.svg deleted file mode 100644 index 890a6cddaa1..00000000000 --- a/src/vs/workbench/browser/media/images/search/clear-alt1.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/media/images/search/collapse-all-alt1.svg b/src/vs/workbench/browser/media/images/search/collapse-all-alt1.svg deleted file mode 100644 index f2e0e5dd5f6..00000000000 --- a/src/vs/workbench/browser/media/images/search/collapse-all-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/search/collapse-alt1.svg b/src/vs/workbench/browser/media/images/search/collapse-alt1.svg deleted file mode 100644 index 829e53760f2..00000000000 --- a/src/vs/workbench/browser/media/images/search/collapse-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/search/exclude-alt1.svg b/src/vs/workbench/browser/media/images/search/exclude-alt1.svg deleted file mode 100644 index 2257ec6dae9..00000000000 --- a/src/vs/workbench/browser/media/images/search/exclude-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/search/expand-alt1.svg b/src/vs/workbench/browser/media/images/search/expand-alt1.svg deleted file mode 100644 index a1085f2ad07..00000000000 --- a/src/vs/workbench/browser/media/images/search/expand-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/search/more-alt1.svg b/src/vs/workbench/browser/media/images/search/more-alt1.svg deleted file mode 100644 index a83faaa6ffb..00000000000 --- a/src/vs/workbench/browser/media/images/search/more-alt1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/browser/media/images/search/refresh-alt1.svg b/src/vs/workbench/browser/media/images/search/refresh-alt1.svg deleted file mode 100644 index a940b8ef4a6..00000000000 --- a/src/vs/workbench/browser/media/images/search/refresh-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/search/regex-alt1.svg b/src/vs/workbench/browser/media/images/search/regex-alt1.svg deleted file mode 100644 index bffb311a5be..00000000000 --- a/src/vs/workbench/browser/media/images/search/regex-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/search/remove-alt1.svg b/src/vs/workbench/browser/media/images/search/remove-alt1.svg deleted file mode 100644 index 64618b61760..00000000000 --- a/src/vs/workbench/browser/media/images/search/remove-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/search/replace-all-alt1.svg b/src/vs/workbench/browser/media/images/search/replace-all-alt1.svg deleted file mode 100644 index 1ef9b63cd60..00000000000 --- a/src/vs/workbench/browser/media/images/search/replace-all-alt1.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/vs/workbench/browser/media/images/search/replace-alt1.svg b/src/vs/workbench/browser/media/images/search/replace-alt1.svg deleted file mode 100644 index 0979845e836..00000000000 --- a/src/vs/workbench/browser/media/images/search/replace-alt1.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/browser/media/images/search/stop-alt1.svg b/src/vs/workbench/browser/media/images/search/stop-alt1.svg deleted file mode 100644 index 52f3aa26ce2..00000000000 --- a/src/vs/workbench/browser/media/images/search/stop-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/search/whole-word-alt1.svg b/src/vs/workbench/browser/media/images/search/whole-word-alt1.svg deleted file mode 100644 index 8a18eed8deb..00000000000 --- a/src/vs/workbench/browser/media/images/search/whole-word-alt1.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/browser/media/images/tree/close-alt1.svg b/src/vs/workbench/browser/media/images/tree/close-alt1.svg deleted file mode 100644 index 3818f7d5350..00000000000 --- a/src/vs/workbench/browser/media/images/tree/close-alt1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/media/images/tree/collapse-alt1.svg b/src/vs/workbench/browser/media/images/tree/collapse-alt1.svg deleted file mode 100644 index f6e6553774e..00000000000 --- a/src/vs/workbench/browser/media/images/tree/collapse-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/tree/dirty-alt1.svg b/src/vs/workbench/browser/media/images/tree/dirty-alt1.svg deleted file mode 100644 index e391c3b0852..00000000000 --- a/src/vs/workbench/browser/media/images/tree/dirty-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/tree/expand-alt1.svg b/src/vs/workbench/browser/media/images/tree/expand-alt1.svg deleted file mode 100644 index a98a85b340e..00000000000 --- a/src/vs/workbench/browser/media/images/tree/expand-alt1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/media/images/tree/expand-empty.svg b/src/vs/workbench/browser/media/images/tree/expand-empty.svg deleted file mode 100644 index 1a0e359a261..00000000000 --- a/src/vs/workbench/browser/media/images/tree/expand-empty.svg +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index a467be6485b..af38fbd57ef 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -161,6 +161,7 @@ body.web { /* START Keyboard Focus Indication Styles */ .monaco-workbench [tabindex="0"]:focus, +.monaco-workbench [tabindex="-1"]:focus, .monaco-workbench .synthetic-focus, .monaco-workbench select:focus, .monaco-workbench input[type="button"]:focus, @@ -174,6 +175,7 @@ body.web { } .monaco-workbench [tabindex="0"]:active, +.monaco-workbench [tabindex="-1"]:active, .monaco-workbench select:active, .monaco-workbench input[type="button"]:active, .monaco-workbench input[type="checkbox"]:active, diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 8be3d84e29e..7b10d5f06c5 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -18,13 +18,12 @@ import { IAction } from 'vs/base/common/actions'; import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IComposite } from 'vs/workbench/common/composite'; -import { ScopedProgressService } from 'vs/workbench/services/progress/browser/localProgressService'; +import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -50,7 +49,7 @@ export interface ICompositeTitleLabel { interface CompositeItem { composite: Composite; disposable: IDisposable; - localProgressService: ILocalProgressService; + progress: IProgressIndicator; } export abstract class CompositePart extends Part { @@ -169,17 +168,18 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const localProgressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive); - const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([ILocalProgressService, localProgressService])); - - const composite = compositeDescriptor.instantiate(compositeInstantiationService); - const disposables = new DisposableStore(); + const composite = compositeDescriptor.instantiate(this.instantiationService); + const disposable = new DisposableStore(); // Remember as Instantiated - this.instantiatedCompositeItems.set(id, { composite, disposable: disposables, localProgressService }); + this.instantiatedCompositeItems.set(id, { + composite, + disposable, + progress: this._register(this.instantiationService.createInstance(CompositeProgressIndicator, this.progressBar, compositeDescriptor.id, isActive)) + }); // Register to title area update events from the composite - disposables.add(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()), this)); + disposable.add(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()), this)); return composite; } @@ -451,10 +451,10 @@ export abstract class CompositePart extends Part { return contentContainer; } - getProgressIndicator(id: string): ILocalProgressService | null { + getProgressIndicator(id: string): IProgressIndicator | null { const compositeItem = this.instantiatedCompositeItems.get(id); - return compositeItem ? compositeItem.localProgressService : null; + return compositeItem ? compositeItem.progress : null; } protected getActions(): ReadonlyArray { diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index b8a8325af39..b2d51bde13c 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -20,6 +20,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: EditorOptions) => Promise; @@ -50,7 +51,8 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { themeService: IThemeService, @IFileService private readonly fileService: IFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, telemetryService, themeService, storageService); @@ -97,7 +99,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { openInternalClb: () => this.handleOpenInternalCallback(input, options), openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), metadataClb: meta => this.handleMetadataChanged(meta) - }); + }, this.instantiationService); } private async handleOpenInternalCallback(input: EditorInput, options: EditorOptions): Promise { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 265f7b286b4..ca8fb69dd8c 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -71,7 +71,6 @@ export abstract class BreadcrumbsConfig { static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath'); static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath'); static SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder'); - static FilterOnType = BreadcrumbsConfig._stub('breadcrumbs.filterOnType'); static FileExcludes = BreadcrumbsConfig._stub('files.exclude'); @@ -161,12 +160,7 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('symbolSortOrder.name', "Show symbol outline in alphabetical order."), localize('symbolSortOrder.type', "Show symbol outline in symbol type order."), ] - }, - // 'breadcrumbs.filterOnType': { - // description: localize('filterOnType', "Controls whether the breadcrumb picker filters or highlights when typing."), - // type: 'boolean', - // default: false - // }, + } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 21cae306f4e..bf2117c79cc 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -282,7 +282,8 @@ export class BreadcrumbsControl { private _onFocusEvent(event: IBreadcrumbsItemEvent): void { if (event.item && this._breadcrumbsPickerShowing) { - return this._widget.setSelection(event.item); + this._breadcrumbsPickerIgnoreOnceItem = undefined; + this._widget.setSelection(event.item); } } @@ -327,6 +328,7 @@ export class BreadcrumbsControl { // show picker let picker: BreadcrumbsPicker; + let pickerAnchor: { x: number; y: number }; let editor = this._getActiveCodeEditor(); let editorDecorations: string[] = []; let editorViewState: ICodeEditorViewState | undefined; @@ -393,34 +395,37 @@ export class BreadcrumbsControl { ); }, getAnchor: () => { - let maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/; - let maxHeight = Math.min(window.innerHeight * 0.7, 300); + if (!pickerAnchor) { + let maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/; + let maxHeight = Math.min(window.innerHeight * 0.7, 300); - let pickerWidth = Math.min(maxInnerWidth, Math.max(240, maxInnerWidth / 4.17)); - let pickerArrowSize = 8; - let pickerArrowOffset: number; + let pickerWidth = Math.min(maxInnerWidth, Math.max(240, maxInnerWidth / 4.17)); + let pickerArrowSize = 8; + let pickerArrowOffset: number; - let data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement); - let y = data.top + data.height + pickerArrowSize; - if (y + maxHeight >= window.innerHeight) { - maxHeight = window.innerHeight - y - 30 /* room for shadow and status bar*/; - } - let x = data.left; - if (x + pickerWidth >= maxInnerWidth) { - x = maxInnerWidth - pickerWidth; - } - if (event.payload instanceof StandardMouseEvent) { - let maxPickerArrowOffset = pickerWidth - 2 * pickerArrowSize; - pickerArrowOffset = event.payload.posx - x; - if (pickerArrowOffset > maxPickerArrowOffset) { - x = Math.min(maxInnerWidth - pickerWidth, x + pickerArrowOffset - maxPickerArrowOffset); - pickerArrowOffset = maxPickerArrowOffset; + let data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement); + let y = data.top + data.height + pickerArrowSize; + if (y + maxHeight >= window.innerHeight) { + maxHeight = window.innerHeight - y - 30 /* room for shadow and status bar*/; } - } else { - pickerArrowOffset = (data.left + (data.width * 0.3)) - x; + let x = data.left; + if (x + pickerWidth >= maxInnerWidth) { + x = maxInnerWidth - pickerWidth; + } + if (event.payload instanceof StandardMouseEvent) { + let maxPickerArrowOffset = pickerWidth - 2 * pickerArrowSize; + pickerArrowOffset = event.payload.posx - x; + if (pickerArrowOffset > maxPickerArrowOffset) { + x = Math.min(maxInnerWidth - pickerWidth, x + pickerArrowOffset - maxPickerArrowOffset); + pickerArrowOffset = maxPickerArrowOffset; + } + } else { + pickerArrowOffset = (data.left + (data.width * 0.3)) - x; + } + picker.show(element, maxHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset)); + pickerAnchor = { x, y }; } - picker.show(element, maxHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset)); - return { x, y }; + return pickerAnchor; }, onHide: (data) => { if (editor) { @@ -605,6 +610,42 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ widget.focusPrev(); } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.focusNextWithPicker', + weight: KeybindingWeight.WorkbenchContrib + 1, + primary: KeyMod.CtrlCmd | KeyCode.RightArrow, + mac: { + primary: KeyMod.Alt | KeyCode.RightArrow, + }, + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey), + handler(accessor) { + const groups = accessor.get(IEditorGroupsService); + const breadcrumbs = accessor.get(IBreadcrumbsService); + const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (!widget) { + return; + } + widget.focusNext(); + } +}); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.focusPreviousWithPicker', + weight: KeybindingWeight.WorkbenchContrib + 1, + primary: KeyMod.CtrlCmd | KeyCode.LeftArrow, + mac: { + primary: KeyMod.Alt | KeyCode.LeftArrow, + }, + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey), + handler(accessor) { + const groups = accessor.get(IEditorGroupsService); + const breadcrumbs = accessor.get(IBreadcrumbsService); + const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (!widget) { + return; + } + widget.focusPrev(); + } +}); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'breadcrumbs.selectFocused', weight: KeybindingWeight.WorkbenchContrib, @@ -664,7 +705,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler(accessor) { const editors = accessor.get(IEditorService); const lists = accessor.get(IListService); - const element = lists.lastFocusedList ? lists.lastFocusedList.getFocus() : undefined; + const element = lists.lastFocusedList ? lists.lastFocusedList.getFocus()[0] : undefined; if (element instanceof OutlineElement) { const outlineElement = OutlineModel.get(element); if (!outlineElement) { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index d2147f4c505..7d0376877c1 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -15,7 +15,7 @@ import { basename, dirname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/breadcrumbscontrol'; import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IConstructorSignature1, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchDataTree, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -102,10 +102,6 @@ export abstract class BreadcrumbsPicker { this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getTheme().getColor(widgetShadow)}`; this._domNode.appendChild(this._treeContainer); - - const filterConfig = BreadcrumbsConfig.FilterOnType.bindTo(this._configurationService); - this._disposables.push(filterConfig); - this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; this._tree = this._createTree(this._treeContainer); @@ -129,13 +125,6 @@ export abstract class BreadcrumbsPicker { this._layout(); })); - // filter on type: state - const cfgFilterOnType = BreadcrumbsConfig.FilterOnType.bindTo(this._configurationService); - this._tree.updateOptions({ filterOnType: cfgFilterOnType.getValue() }); - this._disposables.push(this._tree.onDidUpdateOptions(e => { - this._configurationService.updateValue(cfgFilterOnType.name, e.filterOnType, ConfigurationTarget.MEMORY); - })); - this._domNode.focus(); this._setInput(input).then(() => { @@ -389,7 +378,6 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { this._disposables.push(labels); return this._instantiationService.createInstance(WorkbenchAsyncDataTree, container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { - filterOnType: true, multipleSelectionSupport: false, sorter: new FileSorter(), filter: this._instantiationService.createInstance(FileFilter), @@ -467,12 +455,11 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { [new OutlineGroupRenderer(), this._instantiationService.createInstance(OutlineElementRenderer)], new OutlineDataSource(), { - filterOnType: true, expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, sorter: new OutlineItemComparator(this._getOutlineItemCompareType()), identityProvider: new OutlineIdentityProvider(), - keyboardNavigationLabelProvider: this._instantiationService.createInstance(OutlineNavigationLabelProvider) + keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider() } ) as WorkbenchDataTree; } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index ebb0be6b68b..9451b02835c 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -8,8 +8,6 @@ import * as nls from 'vs/nls'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Action, IAction } from 'vs/base/common/actions'; import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; -import { StatusbarItemDescriptor, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -49,7 +47,6 @@ import { isMacintosh } from 'vs/base/common/platform'; import { AllEditorsPicker, ActiveEditorGroupPicker } from 'vs/workbench/browser/parts/editor/editorPicker'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets'; -import { ZoomStatusbarItem } from 'vs/workbench/browser/parts/editor/resourceViewer'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { toLocalResource } from 'vs/base/common/resources'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; @@ -224,16 +221,6 @@ registerEditorContribution(OpenWorkspaceButtonContribution); // Register Editor Status Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready); -// Register Zoom Status -const statusBar = Registry.as(StatusExtensions.Statusbar); -statusBar.registerStatusbarItem(new StatusbarItemDescriptor( - ZoomStatusbarItem, - 'status.imageZoom', - nls.localize('status.imageZoom', "Image Zoom"), - StatusbarAlignment.RIGHT, - 101 /* to the left of editor status (100) */) -); - // Register Status Actions const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode'); diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index a3c50f7011a..646ecb46f2c 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -11,7 +11,7 @@ import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } fr import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILocalProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -47,11 +47,11 @@ export class EditorControl extends Disposable { private groupView: IEditorGroupView, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ILocalProgressService localProgressService: ILocalProgressService + @IEditorProgressService editorProgressService: IEditorProgressService ) { super(); - this.editorOperation = this._register(new LongRunningOperation(localProgressService)); + this.editorOperation = this._register(new LongRunningOperation(editorProgressService)); } get activeControl(): IVisibleEditor | null { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 5aa9e32bb4b..323f41a55cb 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -20,8 +20,8 @@ import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BAC import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; -import { LocalProgressService } from 'vs/workbench/services/progress/browser/localProgressService'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { EditorProgressService } from 'vs/workbench/services/progress/browser/editorProgressService'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -178,7 +178,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, scopedContextKeyService], - [ILocalProgressService, new LocalProgressService(this.progressBar)] + [IEditorProgressService, new EditorProgressService(this.progressBar)] )); // Context keys diff --git a/src/vs/workbench/browser/parts/editor/media/resourceviewer.css b/src/vs/workbench/browser/parts/editor/media/resourceviewer.css index af8cc51704a..b6f7b107db7 100644 --- a/src/vs/workbench/browser/parts/editor/media/resourceviewer.css +++ b/src/vs/workbench/browser/parts/editor/media/resourceviewer.css @@ -58,10 +58,6 @@ cursor: zoom-out; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a.zoom-statusbar-item { - padding: 0 5px 0 5px; -} - .monaco-resource-viewer .embedded-link, .monaco-resource-viewer .embedded-link:hover { cursor: pointer; diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index 65b7706e60d..a5e644112b5 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -12,16 +12,15 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle import { LRUCache } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; -import { Themable } from 'vs/workbench/common/theme'; -import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Action } from 'vs/base/common/actions'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { memoize } from 'vs/base/common/decorators'; import * as platform from 'vs/base/common/platform'; import { IFileService } from 'vs/platform/files/common/files'; +import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export interface IResourceDescriptor { readonly resource: URI; @@ -81,7 +80,8 @@ export class ResourceViewer { fileService: IFileService, container: HTMLElement, scrollbar: DomScrollableElement, - delegate: ResourceViewerDelegate + delegate: ResourceViewerDelegate, + instantiationService: IInstantiationService, ): ResourceViewerContext { // Ensure CSS class @@ -89,7 +89,7 @@ export class ResourceViewer { // Images if (ResourceViewer.isImageResource(descriptor)) { - return ImageView.create(container, descriptor, fileService, scrollbar, delegate); + return ImageView.create(container, descriptor, fileService, scrollbar, delegate, instantiationService); } // Large Files @@ -120,10 +120,11 @@ class ImageView { descriptor: IResourceDescriptor, fileService: IFileService, scrollbar: DomScrollableElement, - delegate: ResourceViewerDelegate + delegate: ResourceViewerDelegate, + instantiationService: IInstantiationService, ): ResourceViewerContext { if (ImageView.shouldShowImageInline(descriptor)) { - return InlineImageView.create(container, descriptor, fileService, scrollbar, delegate); + return InlineImageView.create(container, descriptor, fileService, scrollbar, delegate, instantiationService); } return LargeImageView.create(container, descriptor, delegate); @@ -234,71 +235,53 @@ class FileSeemsBinaryFileView { type Scale = number | 'fit'; -export class ZoomStatusbarItem extends Themable implements IStatusbarItem { +export class ZoomStatusbarItem extends Disposable { - static instance: ZoomStatusbarItem; - - private showTimeout: any; - - private statusBarItem: HTMLElement; - private onSelectScale?: (scale: Scale) => void; + private statusbarItem?: IStatusbarEntryAccessor; constructor( - @IContextMenuService private readonly contextMenuService: IContextMenuService, + private readonly onSelectScale: (scale: Scale) => void, @IEditorService editorService: IEditorService, - @IThemeService themeService: IThemeService + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IStatusbarService private readonly statusbarService: IStatusbarService, ) { - super(themeService); - - ZoomStatusbarItem.instance = this; - - this._register(editorService.onDidActiveEditorChange(() => this.onActiveEditorChanged())); + super(); + this._register(editorService.onDidActiveEditorChange(() => { + if (this.statusbarItem) { + this.statusbarItem.dispose(); + this.statusbarItem = undefined; + } + })); } - private onActiveEditorChanged(): void { - this.hide(); - this.onSelectScale = undefined; - } + updateStatusbar(scale: Scale): void { + const entry: IStatusbarEntry = { + text: this.zoomLabel(scale) + }; - show(scale: Scale, onSelectScale: (scale: number) => void) { - clearTimeout(this.showTimeout); - this.showTimeout = setTimeout(() => { - this.onSelectScale = onSelectScale; - this.statusBarItem.style.display = 'block'; - this.updateLabel(scale); - }, 0); - } + if (!this.statusbarItem) { + this.statusbarItem = this.statusbarService.addEntry(entry, 'status.imageZoom', nls.localize('status.imageZoom', "Image Zoom"), StatusbarAlignment.RIGHT, 101 /* to the left of editor status (100) */); - hide() { - this.statusBarItem.style.display = 'none'; - } + this._register(this.statusbarItem); - render(container: HTMLElement): IDisposable { - if (!this.statusBarItem && container) { - this.statusBarItem = DOM.append(container, DOM.$('a.zoom-statusbar-item')); - this.statusBarItem.setAttribute('role', 'button'); - this.statusBarItem.style.display = 'none'; - - DOM.addDisposableListener(this.statusBarItem, DOM.EventType.CLICK, () => { + const element = document.getElementById('status.imageZoom')!; + this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, (e: MouseEvent) => { this.contextMenuService.showContextMenu({ - getAnchor: () => container, + getAnchor: () => element, getActions: () => this.zoomActions }); - }); + })); + } else { + this.statusbarItem.update(entry); } - - return this; - } - - private updateLabel(scale: Scale) { - this.statusBarItem.textContent = ZoomStatusbarItem.zoomLabel(scale); } @memoize private get zoomActions(): Action[] { const scales: Scale[] = [10, 5, 2, 1, 0.5, 0.2, 'fit']; return scales.map(scale => - new Action(`zoom.${scale}`, ZoomStatusbarItem.zoomLabel(scale), undefined, undefined, () => { + new Action(`zoom.${scale}`, this.zoomLabel(scale), undefined, undefined, () => { + this.updateStatusbar(scale); if (this.onSelectScale) { this.onSelectScale(scale); } @@ -307,7 +290,7 @@ export class ZoomStatusbarItem extends Themable implements IStatusbarItem { })); } - private static zoomLabel(scale: Scale): string { + private zoomLabel(scale: Scale): string { return scale === 'fit' ? nls.localize('zoom.action.fit.label', 'Whole Image') : `${Math.round(scale * 100)}%`; @@ -361,10 +344,14 @@ class InlineImageView { descriptor: IResourceDescriptor, fileService: IFileService, scrollbar: DomScrollableElement, - delegate: ResourceViewerDelegate + delegate: ResourceViewerDelegate, + @IInstantiationService instantiationService: IInstantiationService, ) { const disposables = new DisposableStore(); + const zoomStatusbarItem = disposables.add(instantiationService.createInstance(ZoomStatusbarItem, + newScale => updateScale(newScale))); + const context: ResourceViewerContext = { layout(dimension: DOM.Dimension) { }, dispose: () => disposables.dispose() @@ -421,9 +408,9 @@ class InlineImageView { }); InlineImageView.imageStateCache.set(cacheKey, { scale: scale, offsetX: newScrollLeft, offsetY: newScrollTop }); - } - ZoomStatusbarItem.instance.show(scale, updateScale); + + zoomStatusbarItem.updateStatusbar(scale); scrollbar.scanDomNode(); } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 667ec403730..8fa00b20595 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -24,7 +24,7 @@ import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDispo import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; @@ -1154,7 +1154,15 @@ export class TabsTitleControl extends TitleControl { } registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { - + // Add border between tabs and breadcrumbs in high contrast mode. + if (theme.type === HIGH_CONTRAST) { + const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder)); + collector.addRule(` + .monaco-workbench div.tabs-and-actions-container { + border-bottom: 1px solid ${borderColor}; + } + `); + } // Styling with Outline color (e.g. high contrast theme) const activeContrastBorderColor = theme.getColor(activeContrastBorder); if (activeContrastBorderColor) { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 39b2c68e696..b2e36ae2e74 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom'; import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; -import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; +import { QUICK_INPUT_BACKGROUND, QUICK_INPUT_FOREGROUND } from 'vs/workbench/common/theme'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { CancellationToken } from 'vs/base/common/cancellation'; import { QuickInputList } from './quickInputList'; @@ -1512,10 +1512,10 @@ export class QuickInputService extends Component implements IQuickInputService { const titleColor = { dark: 'rgba(255, 255, 255, 0.105)', light: 'rgba(0,0,0,.06)', hc: 'black' }[theme.type]; this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : null; this.ui.inputBox.style(theme); - const sideBarBackground = theme.getColor(SIDE_BAR_BACKGROUND); - this.ui.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : null; - const sideBarForeground = theme.getColor(SIDE_BAR_FOREGROUND); - this.ui.container.style.color = sideBarForeground ? sideBarForeground.toString() : null; + const quickInputBackground = theme.getColor(QUICK_INPUT_BACKGROUND); + this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : null; + const quickInputForeground = theme.getColor(QUICK_INPUT_FOREGROUND); + this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : null; const contrastBorderColor = theme.getColor(contrastBorder); this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : null; const widgetShadowColor = theme.getColor(widgetShadow); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 1c1d735ff47..5851bdb62ce 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -34,7 +34,7 @@ import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiat import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; +import { QUICK_INPUT_BACKGROUND, QUICK_INPUT_FOREGROUND } from 'vs/workbench/common/theme'; import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; @@ -188,7 +188,7 @@ export class QuickOpenController extends Component implements IQuickOpenService treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts) } )); - this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: SIDE_BAR_BACKGROUND, foreground: SIDE_BAR_FOREGROUND })); + this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: QUICK_INPUT_BACKGROUND, foreground: QUICK_INPUT_FOREGROUND })); const quickOpenContainer = this.quickOpenWidget.create(); addClass(quickOpenContainer, 'show-file-icons'); diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 940467dab9b..5c4e22b033c 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -75,26 +75,23 @@ padding-right: 10px; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item a { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a { cursor: pointer; display: inline-block; height: 100%; -} - -.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > a { padding: 0 5px 0 5px; white-space: pre; /* gives some degree of styling */ } -.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > a.disabled { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a:hover { + text-decoration: none; +} + +.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a.disabled { pointer-events: none; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-entry span.octicon { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item span.octicon { text-align: center; font-size: 14px; } - -.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { - text-decoration: none; -} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/statusbar/statusbar.ts b/src/vs/workbench/browser/parts/statusbar/statusbar.ts deleted file mode 100644 index 7f1c7b5cb37..00000000000 --- a/src/vs/workbench/browser/parts/statusbar/statusbar.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Registry } from 'vs/platform/registry/common/platform'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; -import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; - -export interface IStatusbarItem { - render(element: HTMLElement): IDisposable; -} - -export class StatusbarItemDescriptor { - readonly syncDescriptor: SyncDescriptor0; - readonly id: string; - readonly name: string; - readonly alignment: StatusbarAlignment; - readonly priority: number; - - constructor( - ctor: IConstructorSignature0, - id: string, - name: string, - alignment?: StatusbarAlignment, - priority?: number - ) { - this.id = id; - this.name = name; - this.syncDescriptor = createSyncDescriptor(ctor); - this.alignment = alignment || StatusbarAlignment.LEFT; - this.priority = priority || 0; - } -} - -export interface IStatusbarRegistry { - - readonly items: StatusbarItemDescriptor[]; - - registerStatusbarItem(descriptor: StatusbarItemDescriptor): void; -} - -class StatusbarRegistry implements IStatusbarRegistry { - - private readonly _items: StatusbarItemDescriptor[] = []; - get items(): StatusbarItemDescriptor[] { return this._items; } - - registerStatusbarItem(descriptor: StatusbarItemDescriptor): void { - this._items.push(descriptor); - } -} - -export const Extensions = { - Statusbar: 'workbench.contributions.statusbar' -}; - -Registry.add(Extensions.Statusbar, new StatusbarRegistry()); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index e4117106882..9eee470b9c8 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -8,11 +8,9 @@ import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; -import { Registry } from 'vs/platform/registry/common/platform'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Part } from 'vs/workbench/browser/part'; -import { IStatusbarRegistry, Extensions } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; @@ -397,7 +395,7 @@ export class StatusbarPart extends Part implements IStatusbarService { private doAddEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor { // Create item - const itemContainer = this.doCreateStatusItem(id, name, alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined])); + const itemContainer = this.doCreateStatusItem(id, name, alignment, priority, ...coalesce([entry.showBeak ? 'has-beak' : undefined])); const item = this.instantiationService.createInstance(StatusbarEntryItem, itemContainer, entry); // Append to parent @@ -450,20 +448,6 @@ export class StatusbarPart extends Part implements IStatusbarService { } private createInitialStatusbarEntries(): void { - const registry = Registry.as(Extensions.Statusbar); - - // Create initial items that were contributed from the registry - for (const { id, name, alignment, priority, syncDescriptor } of registry.items) { - - // Create item - const item = this.instantiationService.createInstance(syncDescriptor); - const itemContainer = this.doCreateStatusItem(id, name, alignment, priority); - this._register(item.render(itemContainer)); - - // Add to view model - const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer }; - this.viewModel.add(viewModelEntry); - } // Add items in order according to alignment this.appendAllStatusbarEntries(); @@ -603,7 +587,7 @@ export class StatusbarPart extends Part implements IStatusbarService { private doCreateStatusItem(id: string, name: string, alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement { const itemContainer = document.createElement('div'); - itemContainer.title = name; + itemContainer.id = id; addClass(itemContainer, 'statusbar-item'); if (extraClasses) { @@ -660,6 +644,7 @@ class StatusbarEntryItem extends Disposable { // Label Container this.labelContainer = document.createElement('a'); + this.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus. // Label this.label = new OcticonLabel(this.labelContainer); @@ -681,12 +666,12 @@ class StatusbarEntryItem extends Disposable { } } - // Update: Tooltip + // Update: Tooltip (on the container, because label can be disabled) if (!this.entry || entry.tooltip !== this.entry.tooltip) { if (entry.tooltip) { - this.labelContainer.title = entry.tooltip; + this.container.title = entry.tooltip; } else { - delete this.labelContainer.title; + delete this.container.title; } } @@ -717,7 +702,7 @@ class StatusbarEntryItem extends Disposable { this.applyColor(this.labelContainer, entry.color); } - // Update: Backgroud + // Update: Background if (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) { if (entry.backgroundColor) { this.applyColor(this.container, entry.backgroundColor, true); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 40c05858aea..1e7c9049c09 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -34,13 +34,15 @@ import { assign } from 'vs/base/common/objects'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { isFullscreen } from 'vs/base/browser/browser'; export abstract class MenubarControl extends Disposable { protected keys = [ 'window.menuBarVisibility', 'window.enableMenuBarMnemonics', - 'window.disableCustomMenuBarAltFocus', + 'window.customMenuBarAltFocus', 'window.nativeTabs' ]; @@ -106,8 +108,6 @@ export abstract class MenubarControl extends Disposable { this.menuUpdater = this._register(new RunOnceScheduler(() => this.doUpdateMenubar(false), 200)); this.notifyUserOfCustomMenubarAccessibility(); - - this.registerListeners(); } protected abstract doUpdateMenubar(firstTime: boolean): void; @@ -300,6 +300,8 @@ export class NativeMenubarControl extends MenubarControl { this.doUpdateMenubar(true); }); + + this.registerListeners(); } protected doUpdateMenubar(firstTime: boolean): void { @@ -457,7 +459,8 @@ export class CustomMenubarControl extends MenubarControl { @IPreferencesService preferencesService: IPreferencesService, @IEnvironmentService environmentService: IEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, - @IThemeService private readonly themeService: IThemeService + @IThemeService private readonly themeService: IThemeService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super( @@ -482,6 +485,8 @@ export class CustomMenubarControl extends MenubarControl { this.recentlyOpened = recentlyOpened; }); + this.registerListeners(); + registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (menubarActiveWindowFgColor) { @@ -605,9 +610,11 @@ export class CustomMenubarControl extends MenubarControl { } private get currentDisableMenuBarAltFocus(): boolean { - let disableMenuBarAltBehavior = this.configurationService.getValue('window.disableCustomMenuBarAltFocus'); - if (typeof disableMenuBarAltBehavior !== 'boolean') { - disableMenuBarAltBehavior = false; + let settingValue = this.configurationService.getValue('window.customMenuBarAltFocus'); + + let disableMenuBarAltBehavior = false; + if (typeof settingValue === 'boolean') { + disableMenuBarAltBehavior = !settingValue; } return disableMenuBarAltBehavior; @@ -642,7 +649,7 @@ export class CustomMenubarControl extends MenubarControl { enableMenuBarMnemonics = true; } - return enableMenuBarMnemonics; + return enableMenuBarMnemonics && (!isWeb || isFullscreen()); } private setupCustomMenubar(firstTime: boolean): void { @@ -750,6 +757,11 @@ export class CustomMenubarControl extends MenubarControl { this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { this.menubar.blur(); })); + + // Mnemonics require fullscreen in web + if (isWeb) { + this._register(this.layoutService.onFullscreenChange(e => this.updateMenubar())); + } } get onVisibilityChange(): Event { diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index 24bc347c337..dbb318045f5 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/style'; -import 'vs/css!./media/icons'; import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -96,6 +95,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (focusOutline) { collector.addRule(` .monaco-workbench [tabindex="0"]:focus, + .monaco-workbench [tabindex="-1"]:focus, .monaco-workbench .synthetic-focus, .monaco-workbench select:focus, .monaco-workbench .monaco-tree.focused.no-focused-item:focus:before, @@ -115,6 +115,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (theme.type === HIGH_CONTRAST) { collector.addRule(` .hc-black [tabindex="0"]:focus, + .hc-black [tabindex="-1"]:focus, .hc-black .synthetic-focus, .hc-black select:focus, .hc-black input[type="button"]:focus, diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 4c03d31203d..1986fb66422 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -8,7 +8,8 @@ import { domContentLoaded, addDisposableListener, EventType } from 'vs/base/brow import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; -import { SimpleLogService, SimpleProductService, SimpleWorkbenchEnvironmentService } from 'vs/workbench/browser/web.simpleservices'; +import { SimpleLogService } from 'vs/workbench/browser/web.simpleservices'; +import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { Workbench } from 'vs/workbench/browser/workbench'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; @@ -28,24 +29,12 @@ import { URI } from 'vs/base/common/uri'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache'; -import { ConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; import { WebResources } from 'vs/workbench/browser/web.resources'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/browser/signService'; import { hash } from 'vs/base/common/hash'; -import { joinPath } from 'vs/base/common/resources'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; - -interface IWindowConfiguration { - remoteAuthority: string; - - userDataUri: URI; - - webviewEndpoint?: string; - - folderUri?: URI; - workspaceUri?: URI; -} +import { ProductService } from 'vs/platform/product/browser/productService'; class CodeRendererMain extends Disposable { @@ -53,7 +42,7 @@ class CodeRendererMain extends Disposable { constructor( private readonly domElement: HTMLElement, - private readonly configuration: IWindowConfiguration + private readonly configuration: IWorkbenchConstructionOptions ) { super(); } @@ -97,21 +86,22 @@ class CodeRendererMain extends Disposable { serviceCollection.set(ILogService, logService); // Environment - const environmentService = this.createEnvironmentService(); + const environmentService = new BrowserWorkbenchEnvironmentService(this.configuration); serviceCollection.set(IWorkbenchEnvironmentService, environmentService); // Product - const productService = new SimpleProductService(); + const productService = new ProductService(); serviceCollection.set(IProductService, productService); // Remote const remoteAuthorityResolverService = new RemoteAuthorityResolverService(); serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); - // Sign + // Signing const signService = new SignService(); serviceCollection.set(ISignService, signService); + // Remote Agent const remoteAgentService = this._register(new RemoteAgentService(environmentService, productService, remoteAuthorityResolverService, signService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); @@ -123,6 +113,7 @@ class CodeRendererMain extends Disposable { if (connection) { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); + fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } @@ -144,25 +135,8 @@ class CodeRendererMain extends Disposable { return { serviceCollection, logService }; } - private createEnvironmentService(): IWorkbenchEnvironmentService { - const environmentService = new SimpleWorkbenchEnvironmentService(); - environmentService.appRoot = '/web/'; - environmentService.args = { _: [] }; - environmentService.appSettingsHome = joinPath(this.configuration.userDataUri, 'User'); - environmentService.settingsResource = joinPath(environmentService.appSettingsHome, 'settings.json'); - environmentService.keybindingsResource = joinPath(environmentService.appSettingsHome, 'keybindings.json'); - environmentService.logsPath = '/web/logs'; - environmentService.debugExtensionHost = { - port: null, - break: false - }; - environmentService.webviewEndpoint = this.configuration.webviewEndpoint; - - return environmentService; - } - private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { - const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache() }, new ConfigurationFileService(fileService), remoteAgentService); + const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache() }, fileService, remoteAgentService); try { await workspaceService.initialize(payload); @@ -180,12 +154,12 @@ class CodeRendererMain extends Disposable { // Multi-root workspace if (this.configuration.workspaceUri) { - return { id: hash(this.configuration.workspaceUri.toString()).toString(16), configPath: this.configuration.workspaceUri }; + return { id: hash(URI.revive(this.configuration.workspaceUri).toString()).toString(16), configPath: URI.revive(this.configuration.workspaceUri) }; } // Single-folder workspace if (this.configuration.folderUri) { - return { id: hash(this.configuration.folderUri.toString()).toString(16), folder: this.configuration.folderUri }; + return { id: hash(URI.revive(this.configuration.folderUri).toString()).toString(16), folder: URI.revive(this.configuration.folderUri) }; } return { id: 'empty-window' }; @@ -193,15 +167,7 @@ class CodeRendererMain extends Disposable { } export function main(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { - const renderer = new CodeRendererMain( - domElement, - { - userDataUri: URI.revive(options.userDataUri), - remoteAuthority: options.remoteAuthority, - webviewEndpoint: options.webviewEndpoint, - folderUri: options.folderUri ? URI.revive(options.folderUri) : undefined, - workspaceUri: options.workspaceUri ? URI.revive(options.workspaceUri) : undefined, - }); + const renderer = new CodeRendererMain(domElement, options); return renderer.open(); } \ No newline at end of file diff --git a/src/vs/workbench/browser/web.resources.ts b/src/vs/workbench/browser/web.resources.ts index 8d1d22a8264..ca6c82ef787 100644 --- a/src/vs/workbench/browser/web.resources.ts +++ b/src/vs/workbench/browser/web.resources.ts @@ -10,20 +10,25 @@ import { getMediaMime } from 'vs/base/common/mime'; export class WebResources { private readonly _regexp = /url\(('|")?(vscode-remote:\/\/.*?)\1\)/g; - private readonly _cache = new Map(); + private readonly _urlCache = new Map(); + private readonly _requestCache = new Map>(); private readonly _observer: MutationObserver; constructor(@IFileService private readonly _fileService: IFileService) { - this._observer = new MutationObserver(r => this._handleMutation(r)); - // todo@joh add observer to more than head-element // todo@joh explore alternative approach - this._observer.observe(document.head, { subtree: true, childList: true }); + this._observer = new MutationObserver(r => this._handleMutation(r)); + this._observer.observe(document, { + subtree: true, + childList: true, + attributes: true, + attributeFilter: ['style'] + }); } dispose(): void { this._observer.disconnect(); - this._cache.forEach(value => URL.revokeObjectURL(value)); + this._urlCache.forEach(value => URL.revokeObjectURL(value)); } private _handleMutation(records: MutationRecord[]): void { @@ -39,62 +44,73 @@ export class WebResources { this._handleStyleNode(node); } }); + } else if (record.type === 'attributes') { + // style-attribute + this._handleAttrMutation(record.target); } } } private _handleStyleNode(target: Node): void { - - if (!target.textContent) { - return; + if (target.textContent && target.textContent.indexOf('vscode-remote://') >= 0) { + const content = target.textContent; + this._rewriteUrls(content).then(value => { + if (content === target.textContent) { + target.textContent = value; + } + }).catch(e => { + console.error(e); + }); } + } + + private _handleAttrMutation(target: Node): void { + const styleValue = (target).getAttribute('style'); + if (styleValue && styleValue.indexOf('vscode-remote://') >= 0) { + this._rewriteUrls(styleValue).then(value => { + if (value !== styleValue) { + (target).setAttribute('style', value); + } + }).catch(e => { + console.error(e); + }); + } + } + + private async _rewriteUrls(textContent: string): Promise { const positions: number[] = []; const promises: Promise[] = []; let match: RegExpMatchArray | null = null; - while (match = this._regexp.exec(target.textContent)) { + while (match = this._regexp.exec(textContent)) { const remoteUrl = match[2]; positions.push(match.index! + 'url('.length + (typeof match[1] === 'string' ? match[1].length : 0)); positions.push(remoteUrl.length); - if (this._cache.has(remoteUrl)) { - promises.push(Promise.resolve()); - - } else { - const uri = URI.parse(remoteUrl, true); - promises.push(this._fileService.readFile(uri).then(file => { - this._cache.set(remoteUrl, URL.createObjectURL(new Blob( - [file.value.buffer], { type: getMediaMime(uri.path) } - ))); - })); + if (!this._urlCache.has(remoteUrl)) { + let request = this._requestCache.get(remoteUrl); + if (!request) { + const uri = URI.parse(remoteUrl, true); + request = this._fileService.readFile(uri).then(file => { + const blobUrl = URL.createObjectURL(new Blob([file.value.buffer], { type: getMediaMime(uri.path) })); + this._urlCache.set(remoteUrl, blobUrl); + }); + this._requestCache.set(remoteUrl, request); + } + promises.push(request); } } - if (promises.length === 0) { - return; + let content = textContent; + await Promise.all(promises); + for (let i = positions.length - 1; i >= 0; i -= 2) { + const start = positions[i - 1]; + const len = positions[i]; + const url = this._urlCache.get(content.substr(start, len)); + content = content.substring(0, start) + url + content.substring(start + len); } - - let content = target.textContent; - - Promise.all(promises).then(() => { - - if (target.textContent !== content) { - return; - } - - for (let i = positions.length - 1; i >= 0; i -= 2) { - const start = positions[i - 1]; - const len = positions[i]; - const url = this._cache.get(content.substr(start, len)); - content = content.substring(0, start) + url + content.substring(start + len); - } - - target.textContent = content; - - }).catch(e => { - console.error(e); - }); + return content; } } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index b9eb0073a01..b253e573ae5 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -4,43 +4,37 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; +import * as browser from 'vs/base/browser/browser'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; import { ITextSnapshot } from 'vs/editor/common/model'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { keys } from 'vs/base/common/map'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // tslint:disable-next-line: import-patterns no-standalone-editor import { IDownloadService } from 'vs/platform/download/common/download'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IExtensionHostDebugParams, IDebugParams } from 'vs/platform/environment/common/environment'; import { IExtensionGalleryService, IQueryOptions, IGalleryExtension, InstallOperation, StatisticType, ITranslation, IGalleryExtensionVersion, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata, IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation, IExtensionEnablementService, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IPager } from 'vs/base/common/paging'; import { IExtensionManifest, ExtensionType, ExtensionIdentifier, IExtension } from 'vs/platform/extensions/common/extensions'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; import { ITelemetryService, ITelemetryData, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; -import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; -import { ILogService, LogLevel, ConsoleLogService } from 'vs/platform/log/common/log'; -import { ShutdownReason, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IProductService } from 'vs/platform/product/common/product'; +import { ConsoleLogService } from 'vs/platform/log/common/log'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope, IWillSaveStateEvent, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IUpdateService, State } from 'vs/platform/update/common/update'; -import { IWindowConfiguration, IPath, IPathsToWaitFor, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen, IMessageBoxResult, IWindowsService, IOpenSettings } from 'vs/platform/windows/common/windows'; -import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen, IMessageBoxResult, IWindowsService, IOpenSettings, IWindowSettings } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { ExportData } from 'vs/base/common/performance'; import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IReloadSessionEvent, IExtensionHostDebugService, ICloseSessionEvent, IAttachSessionEvent, ILogToSessionEvent, ITerminateSessionEvent } from 'vs/workbench/services/extensions/common/extensionHostDebug'; import { IRemoteConsoleLog } from 'vs/base/common/console'; // tslint:disable-next-line: import-patterns -import { State as DebugState, IDebugService, IDebugSession, IConfigurationManager, IStackFrame, IThread, IViewModel, IExpression, IFunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; // tslint:disable-next-line: import-patterns import { IExtensionsWorkbenchService, IExtension as IExtension2 } from 'vs/workbench/contrib/extensions/common/extensions'; // tslint:disable-next-line: import-patterns @@ -49,6 +43,16 @@ import { ICommentService, IResourceCommentThreadEvent, IWorkspaceCommentThreadsE import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { CommentingRanges } from 'vs/editor/common/modes'; import { Range } from 'vs/editor/common/core/range'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { pathsToEditors } from 'vs/workbench/common/editor'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { IProcessEnvironment } from 'vs/base/common/platform'; //#region Backup File @@ -169,65 +173,6 @@ export class SimpleDownloadService implements IDownloadService { registerSingleton(IDownloadService, SimpleDownloadService, true); -//#endregion - -//#region Environment - -export class SimpleWorkbenchEnvironmentService implements IWorkbenchEnvironmentService { - configuration: IWindowConfiguration = new SimpleWindowConfiguration(); - untitledWorkspacesHome: URI; - extensionTestsLocationURI?: URI; - _serviceBrand: any; - args: any; - execPath: string; - cliPath: string; - appRoot: string; - userHome: string; - userDataPath: string; - appNameLong: string = 'Visual Studio Code - Web'; - appQuality?: string; - appSettingsHome: URI; - settingsResource: URI; - keybindingsResource: URI; - machineSettingsHome: URI; - machineSettingsResource: URI; - settingsSearchBuildId?: number; - settingsSearchUrl?: string; - globalStorageHome: string; - workspaceStorageHome: string; - backupHome: string; - backupWorkspacesPath: string; - workspacesHome: string; - isExtensionDevelopment: boolean; - disableExtensions: boolean | string[]; - builtinExtensionsPath: string; - extensionsPath: string; - extensionDevelopmentLocationURI?: URI[]; - extensionTestsPath?: string; - debugExtensionHost: IExtensionHostDebugParams; - debugSearch: IDebugParams; - logExtensionHostCommunication: boolean; - isBuilt: boolean; - wait: boolean; - status: boolean; - log?: string; - logsPath: string; - verbose: boolean; - skipGettingStarted: boolean; - skipReleaseNotes: boolean; - skipAddToRecentlyOpened: boolean; - mainIPCHandle: string; - sharedIPCHandle: string; - nodeCachedDataDir?: string; - installSourcePath: string; - disableUpdates: boolean; - disableCrashReporter: boolean; - driverHandle?: string; - driverVerbose: boolean; - webviewEndpoint?: string; -} - - //#endregion //#region Extension Gallery @@ -298,86 +243,6 @@ registerSingleton(IExtensionGalleryService, SimpleExtensionGalleryService, true) //#endregion -//#region IDebugService -export class SimpleDebugService implements IDebugService { - _serviceBrand: any; - state: DebugState; - onDidChangeState: Event = Event.None; - onDidNewSession: Event = Event.None; - onWillNewSession: Event = Event.None; - onDidEndSession: Event = Event.None; - getConfigurationManager(): IConfigurationManager { - return new class implements IConfigurationManager { - canSetBreakpointsIn: any; - selectedConfiguration: any; - selectConfiguration: any; - getLaunches: any; - getLaunch: any; - onDidSelectConfiguration: Event; - activateDebuggers: any; - hasDebugConfigurationProvider: any; - registerDebugConfigurationProvider: any; - unregisterDebugConfigurationProvider: any; - registerDebugAdapterDescriptorFactory: any; - unregisterDebugAdapterDescriptorFactory: any; - resolveConfigurationByProviders: any; - getDebugAdapterDescriptor: any; - registerDebugAdapterFactory() { return Disposable.None; } - createDebugAdapter: any; - substituteVariables: any; - runInTerminal: any; - }; - } - focusStackFrame: any; - addBreakpoints: any; - updateBreakpoints: any; - enableOrDisableBreakpoints: any; - setBreakpointsActivated: any; - removeBreakpoints: any; - addFunctionBreakpoint: any; - renameFunctionBreakpoint: any; - removeFunctionBreakpoints: any; - sendAllBreakpoints: any; - addWatchExpression: any; - renameWatchExpression: any; - moveWatchExpression: any; - removeWatchExpressions: any; - startDebugging: any; - restartSession: any; - stopSession: any; - sourceIsNotAvailable: any; - getModel: any; - getViewModel(): IViewModel { - return new class implements IViewModel { - focusedSession: IDebugSession | undefined; - focusedThread: IThread | undefined; - focusedStackFrame: IStackFrame | undefined; - getSelectedExpression(): IExpression | undefined { - throw new Error('Method not implemented.'); - } - getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined { - throw new Error('Method not implemented.'); - } - setSelectedExpression(expression: IExpression | undefined): void { - throw new Error('Method not implemented.'); - } - setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void { - throw new Error('Method not implemented.'); - } - isMultiSessionView(): boolean { - throw new Error('Method not implemented.'); - } - onDidFocusSession: Event = Event.None; - onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; }> = Event.None; - onDidSelectExpression: Event = Event.None; - getId(): string { - throw new Error('Method not implemented.'); - } - }; - } -} -registerSingleton(IDebugService, SimpleDebugService, true); - //#endregion IExtensionsWorkbenchService export class SimpleExtensionsWorkbenchService implements IExtensionsWorkbenchService { _serviceBrand: any; @@ -537,12 +402,13 @@ export class SimpleExtensionManagementService implements IExtensionManagementSer return Promise.resolve(undefined); } - install(vsix: URI): Promise { + install(vsix: URI): Promise { // @ts-ignore return Promise.resolve(undefined); } - installFromGallery(extension: IGalleryExtension): Promise { + installFromGallery(extension: IGalleryExtension): Promise { + // @ts-ignore return Promise.resolve(undefined); } @@ -597,55 +463,6 @@ registerSingleton(IExtensionUrlHandler, SimpleExtensionURLHandler, true); //#endregion -//#region Lifecycle - -export class SimpleLifecycleService extends AbstractLifecycleService { - - _serviceBrand: any; - - constructor( - @ILogService readonly logService: ILogService - ) { - super(logService); - - this.registerListeners(); - } - - private registerListeners(): void { - window.onbeforeunload = () => this.beforeUnload(); - } - - private beforeUnload(): string { - - // Before Shutdown - this._onBeforeShutdown.fire({ - veto(value) { - if (value === true) { - console.warn(new Error('Preventing onBeforeUnload currently not supported')); - } else if (value instanceof Promise) { - console.warn(new Error('Long running onBeforeShutdown currently not supported')); - } - }, - reason: ShutdownReason.QUIT - }); - - // Will Shutdown - this._onWillShutdown.fire({ - join() { - console.warn(new Error('Long running onWillShutdown currently not supported')); - }, - reason: ShutdownReason.QUIT - }); - - // @ts-ignore - return null; - } -} - -registerSingleton(ILifecycleService, SimpleLifecycleService); - -//#endregion - //#region Log export class SimpleLogService extends ConsoleLogService { } @@ -673,12 +490,13 @@ export class SimpleMultiExtensionsManagementService implements IExtensionManagem return Promise.resolve(undefined); } - install(vsix: URI): Promise { + install(vsix: URI): Promise { // @ts-ignore return Promise.resolve(undefined); } - installFromGallery(extension: IGalleryExtension): Promise { + installFromGallery(extension: IGalleryExtension): Promise { + // @ts-ignore return Promise.resolve(undefined); } @@ -708,23 +526,6 @@ export class SimpleMultiExtensionsManagementService implements IExtensionManagem //#endregion -//#region Product - -export class SimpleProductService implements IProductService { - - _serviceBrand: any; - - version: string = '1.35.0'; - commit?: string; - nameLong: string = ''; - urlProtocol: string = ''; - extensionAllowedProposedApi: string[] = []; - uiExtensions?: string[]; - enableTelemetry: boolean = false; -} - -//#endregion - //#region Request export const IRequestService = createDecorator('requestService'); @@ -748,9 +549,106 @@ export class SimpleRequestService implements IRequestService { //#region Storage -export class SimpleStorageService extends InMemoryStorageService { } +export class LocalStorageService extends Disposable implements IStorageService { + _serviceBrand = undefined; -registerSingleton(IStorageService, SimpleStorageService); + private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); + get onDidChangeStorage(): Event { return this._onDidChangeStorage.event; } + + private readonly _onWillSaveState: Emitter = this._register(new Emitter()); + get onWillSaveState(): Event { return this._onWillSaveState.event; } + + constructor( + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @ILifecycleService lifecycleService: ILifecycleService + ) { + super(); + + this._register(lifecycleService.onBeforeShutdown(() => this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }))); + } + + private toKey(key: string, scope: StorageScope): string { + if (scope === StorageScope.GLOBAL) { + return `global://${key}`; + } + + return `workspace://${this.workspaceContextService.getWorkspace().id}/${key}`; + } + + get(key: string, scope: StorageScope, fallbackValue: string): string; + get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined { + const value = window.localStorage.getItem(this.toKey(key, scope)); + + if (isUndefinedOrNull(value)) { + return fallbackValue; + } + + return value; + } + + getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean; + getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined { + const value = window.localStorage.getItem(this.toKey(key, scope)); + + if (isUndefinedOrNull(value)) { + return fallbackValue; + } + + return value === 'true'; + } + + getNumber(key: string, scope: StorageScope, fallbackValue: number): number; + getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined { + const value = window.localStorage.getItem(this.toKey(key, scope)); + + if (isUndefinedOrNull(value)) { + return fallbackValue; + } + + return parseInt(value, 10); + } + + store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise { + + // We remove the key for undefined/null values + if (isUndefinedOrNull(value)) { + return this.remove(key, scope); + } + + // Otherwise, convert to String and store + const valueStr = String(value); + + // Return early if value already set + const currentValue = window.localStorage.getItem(this.toKey(key, scope)); + if (currentValue === valueStr) { + return Promise.resolve(); + } + + // Update in cache + window.localStorage.setItem(this.toKey(key, scope), valueStr); + + // Events + this._onDidChangeStorage.fire({ scope, key }); + + return Promise.resolve(); + } + + remove(key: string, scope: StorageScope): Promise { + const wasDeleted = window.localStorage.getItem(this.toKey(key, scope)); + window.localStorage.removeItem(this.toKey(key, scope)); + + if (!wasDeleted) { + return Promise.resolve(); // Return early if value already deleted + } + + // Events + this._onDidChangeStorage.fire({ scope, key }); + + return Promise.resolve(); + } +} + +registerSingleton(IStorageService, LocalStorageService); //#endregion @@ -766,6 +664,10 @@ export class SimpleTelemetryService implements ITelemetryService { return Promise.resolve(undefined); } + publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as ITelemetryData); + } + setEnabled(value: boolean): void { } @@ -836,48 +738,7 @@ registerSingleton(IURLService, SimpleURLService); //#region Window -export class SimpleWindowConfiguration implements IWindowConfiguration { - _: any[]; - machineId: string; - windowId: number; - logLevel: LogLevel; - - mainPid: number; - - appRoot: string; - execPath: string; - isInitialStartup?: boolean; - - userEnv: IProcessEnvironment; - nodeCachedDataDir?: string; - - backupPath?: string; - - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - - remoteAuthority: string = document.location.host; - - zoomLevel?: number; - fullscreen?: boolean; - maximized?: boolean; - highContrast?: boolean; - frameless?: boolean; - accessibilitySupport?: boolean; - partsSplashPath?: string; - - perfStartTime?: number; - perfAppReady?: number; - perfWindowLoadTime?: number; - perfEntries: ExportData; - - filesToOpenOrCreate?: IPath[]; - filesToDiff?: IPath[]; - filesToWait?: IPathsToWaitFor; - termProgram?: string; -} - -export class SimpleWindowService implements IWindowService { +export class SimpleWindowService extends Disposable implements IWindowService { _serviceBrand: any; @@ -888,6 +749,30 @@ export class SimpleWindowService implements IWindowService { readonly windowId = 0; + constructor( + @IEditorService private readonly editorService: IEditorService, + @IFileService private readonly fileService: IFileService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + + this._register(addDisposableListener(document, EventType.FULLSCREEN_CHANGE, () => { + if (document.fullscreenElement || (document).webkitFullscreenElement) { + browser.setFullscreen(true); + } else { + browser.setFullscreen(false); + } + })); + + this._register(addDisposableListener(document, EventType.WK_FULLSCREEN_CHANGE, () => { + if (document.fullscreenElement || (document).webkitFullscreenElement || (document).webkitIsFullScreen) { + browser.setFullscreen(true); + } else { + browser.setFullscreen(false); + } + })); + } + isFocused(): Promise { return Promise.resolve(this.hasFocus); } @@ -913,6 +798,8 @@ export class SimpleWindowService implements IWindowService { } reloadWindow(): Promise { + window.location.reload(); + return Promise.resolve(); } @@ -932,7 +819,40 @@ export class SimpleWindowService implements IWindowService { return Promise.resolve(undefined); } - toggleFullScreen(): Promise { + toggleFullScreen(target?: HTMLElement): Promise { + if (!target) { + return Promise.resolve(); + } + + // Chromium + if ((document).fullscreen !== undefined) { + if (!(document).fullscreen) { + + return (target).requestFullscreen().catch(() => { + // if it fails, chromium throws an exception with error undefined. + // re https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen + console.warn('Toggle Full Screen failed'); + }); + } else { + return document.exitFullscreen().catch(() => { + console.warn('Exit Full Screen failed'); + }); + } + } + + // Safari and Edge 14 are all using webkit prefix + if ((document).webkitIsFullScreen !== undefined) { + try { + if (!(document).webkitIsFullScreen) { + (target).webkitRequestFullscreen(); // it's async, but doesn't return a real promise. + } else { + (document).webkitExitFullscreen(); // it's async, but doesn't return a real promise. + } + } catch { + console.warn('Enter/Exit Full Screen failed'); + } + } + return Promise.resolve(); } @@ -963,10 +883,44 @@ export class SimpleWindowService implements IWindowService { return Promise.resolve(); } - openWindow(_uris: IURIToOpen[], _options?: IOpenSettings): Promise { + async openWindow(_uris: IURIToOpen[], _options?: IOpenSettings): Promise { + const { openFolderInNewWindow } = this.shouldOpenNewWindow(_options); + for (let i = 0; i < _uris.length; i++) { + const uri = _uris[i]; + if ('folderUri' in uri) { + const newAddress = `${document.location.origin}/?folder=${uri.folderUri.path}`; + if (openFolderInNewWindow) { + window.open(newAddress); + } else { + window.location.href = newAddress; + } + } + if ('workspaceUri' in uri) { + const newAddress = `${document.location.origin}/?workspace=${uri.workspaceUri.path}`; + if (openFolderInNewWindow) { + window.open(newAddress); + } else { + window.location.href = newAddress; + } + } + if ('fileUri' in uri) { + const inputs: IResourceEditor[] = await pathsToEditors([uri], this.fileService); + this.editorService.openEditors(inputs); + } + } return Promise.resolve(); } + private shouldOpenNewWindow(_options: IOpenSettings = {}): { openFolderInNewWindow: boolean } { + const windowConfig = this.configurationService.getValue('window'); + const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */; + let openFolderInNewWindow = !!_options.forceNewWindow && !_options.forceReuseWindow; + if (!_options.forceNewWindow && !_options.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) { + openFolderInNewWindow = (openFolderInNewWindowConfig === 'on'); + } + return { openFolderInNewWindow }; + } + closeWindow(): Promise { return Promise.resolve(); } @@ -1166,6 +1120,10 @@ export class SimpleWindowsService implements IWindowsService { return Promise.resolve(); } + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + return Promise.resolve(); + } + getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { return Promise.resolve([]); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 07fe800bd64..e13b2539b22 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -6,7 +6,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as nls from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; // Configuration (function registerConfiguration(): void { @@ -180,12 +180,6 @@ import { isMacintosh } from 'vs/base/common/platform'; 'default': true, 'description': nls.localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") }, - // TODO @misolori remove before shipping stable - 'workbench.iconExploration.enabled': { - 'type': 'boolean', - 'default': false, - 'description': nls.localize('iconExplorationEnabled', "Controls the visibility of the icon exploration in the workbench.") - }, 'workbench.view.alwaysShowHeaderActions': { 'type': 'boolean', 'default': false, @@ -278,6 +272,46 @@ import { isMacintosh } from 'vs/base/common/platform'; 'type': 'string', 'default': isMacintosh ? '${activeEditorShort}${separator}${rootName}' : '${dirty}${activeEditorShort}${separator}${rootName}${separator}${appName}', 'markdownDescription': windowTitleDescription + }, + 'window.menuBarVisibility': { + 'type': 'string', + 'enum': ['default', 'visible', 'toggle', 'hidden'], + 'enumDescriptions': [ + nls.localize('window.menuBarVisibility.default', "Menu is only hidden in full screen mode."), + nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), + nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), + nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden.") + ], + 'default': 'default', + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('menuBarVisibility', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it. By default, the menu bar will be visible, unless the window is full screen."), + 'included': isWindows || isLinux || isWeb + }, + 'window.enableMenuBarMnemonics': { + 'type': 'boolean', + 'default': true, + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('enableMenuBarMnemonics', "Controls whether the main menus can be opened via Alt-key shortcuts. Disabling mnemonics allows to bind these Alt-key shortcuts to editor commands instead."), + 'included': isWindows || isLinux + }, + 'window.customMenuBarAltFocus': { + 'type': 'boolean', + 'default': true, + 'scope': ConfigurationScope.APPLICATION, + 'markdownDescription': nls.localize('customMenuBarAltFocus', "Controls whether the menu bar will be focused by pressing the Alt-key. This setting has no effect on toggling the menu bar with the Alt-key."), + 'included': isWindows || isLinux || isWeb + }, + 'window.openFoldersInNewWindow': { + 'type': 'string', + 'enum': ['on', 'off', 'default'], + 'enumDescriptions': [ + nls.localize('window.openFoldersInNewWindow.on', "Folders will open in a new window."), + nls.localize('window.openFoldersInNewWindow.off', "Folders will replace the last active window."), + nls.localize('window.openFoldersInNewWindow.default', "Folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu).") + ], + 'default': 'default', + 'scope': ConfigurationScope.APPLICATION, + 'markdownDescription': nls.localize('openFoldersInNewWindow', "Controls whether folders should open in a new window or replace the last active window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") } } }); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 54b078b2755..005c1bb0ec6 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -6,10 +6,9 @@ import 'vs/workbench/browser/style'; import { localize } from 'vs/nls'; -import { setFileNameComparer } from 'vs/base/common/comparers'; import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; import { addClasses, addClass, removeClasses } from 'vs/base/browser/dom'; -import { runWhenIdle, IdleValue } from 'vs/base/common/async'; +import { runWhenIdle } from 'vs/base/common/async'; import { getZoomLevel } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; @@ -114,15 +113,6 @@ export class Workbench extends Layout { // Configure emitter leak warning threshold setGlobalLeakWarningThreshold(175); - // Setup Intl for comparers - setFileNameComparer(new IdleValue(() => { - const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); - return { - collator: collator, - collatorIsNumeric: collator.resolvedOptions().numeric - }; - })); - // ARIA setARIAContainer(document.body); diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 8bb1a35ebf2..2d453924e8a 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -437,6 +437,20 @@ export const SIDE_BAR_SECTION_HEADER_BORDER = registerColor('sideBarSectionHeade }, nls.localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search.")); +// < --- Quick Input -- > + +export const QUICK_INPUT_BACKGROUND = registerColor('quickInput.background', { + dark: SIDE_BAR_BACKGROUND, + light: SIDE_BAR_BACKGROUND, + hc: SIDE_BAR_BACKGROUND +}, nls.localize('quickInputBackground', "Quick Input background color. The Quick Input widget is the container for views like the color theme picker")); + +export const QUICK_INPUT_FOREGROUND = registerColor('quickInput.foreground', { + dark: SIDE_BAR_FOREGROUND, + light: SIDE_BAR_FOREGROUND, + hc: SIDE_BAR_FOREGROUND +}, nls.localize('quickInputForeground', "Quick Input foreground color. The Quick Input widget is the container for views like the color theme picker")); + // < --- Title Bar --- > export const TITLE_BAR_ACTIVE_FOREGROUND = registerColor('titleBar.activeForeground', { diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts index b66f6d7b093..381de357c7e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts @@ -9,6 +9,10 @@ import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IUntitledResourceInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Action } from 'vs/base/common/actions'; class InspectKeyMap extends EditorAction { @@ -30,3 +34,24 @@ class InspectKeyMap extends EditorAction { } registerEditorAction(InspectKeyMap); + +class InspectKeyMapJSON extends Action { + public static readonly ID = 'workbench.action.inspectKeyMappingsJSON'; + public static readonly LABEL = nls.localize('workbench.action.inspectKeyMapJSON', "Inspect Key Mappings (JSON)"); + + constructor( + id: string, + label: string, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IEditorService private readonly _editorService: IEditorService + ) { + super(id, label); + } + + public run(): Promise { + return this._editorService.openEditor({ contents: this._keybindingService._dumpDebugInfoJSON(), options: { pinned: true } } as IUntitledResourceInput); + } +} + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(new SyncActionDescriptor(InspectKeyMapJSON, InspectKeyMapJSON.ID, InspectKeyMapJSON.LABEL), 'Developer: Inspect Key Mappings (JSON)', nls.localize('developer', "Developer")); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 850b60dcf4a..84afaa1d2f1 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -539,7 +539,7 @@ if (isMacintosh) { command: { id, title, - iconLocation: { dark: URI.parse(require.toUrl(`vs/workbench/contrib/debug/electron-browser/media/${icon}`)) } + iconLocation: { dark: URI.parse(require.toUrl(`vs/workbench/contrib/debug/browser/media/${icon}`)) } }, when, group: '9_debug', diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index be84b6827f3..0f16da0f89f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -323,6 +323,7 @@ export class AddWatchExpressionAction extends AbstractDebugAction { constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action add-watch-expression', debugService, keybindingService); this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); + this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement())); } public run(): Promise { @@ -331,7 +332,8 @@ export class AddWatchExpressionAction extends AbstractDebugAction { } protected isEnabled(state: State): boolean { - return super.isEnabled(state) && this.debugService.getModel().getWatchExpressions().every(we => !!we.name); + const focusedExpression = this.debugService.getViewModel().getSelectedExpression(); + return super.isEnabled(state) && this.debugService.getModel().getWatchExpressions().every(we => !!we.name && we !== focusedExpression); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 885cb431f7d..95010a4ea63 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -129,7 +129,7 @@ export function registerCommands(): void { id = pick._id; } - return await stackFrame.thread.session.goto(stackFrame.thread.threadId, id); + return await stackFrame.thread.session.goto(stackFrame.thread.threadId, id).catch(e => notificationService.warn(e)); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index af9817c9807..317b77e3e78 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -73,8 +73,8 @@ export class ConfigurationManager implements IConfigurationManager { this.adapterDescriptorFactories = []; this.debuggers = []; this.toDispose = []; - this.registerListeners(lifecycleService); this.initLaunches(); + this.registerListeners(lifecycleService); const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop(); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/debug/browser/debugHelperService.ts b/src/vs/workbench/contrib/debug/browser/debugHelperService.ts index c7fa5b96cd1..be123b31a5e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHelperService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHelperService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ServiceIdentifier, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITerminalLauncher, IDebugHelperService, ILaunchVSCodeArguments } from 'vs/workbench/contrib/debug/common/debug'; +import { ITerminalLauncher, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -17,10 +17,6 @@ export class BrowserDebugHelperService implements IDebugHelperService { throw new Error('Method createTerminalLauncher not implemented.'); } - launchVsCode(vscodeArgs: ILaunchVSCodeArguments): Promise { - throw new Error('Method launchVsCode not implemented.'); - } - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { return undefined; } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 3a1d1b36ed6..b8c811771f9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -794,8 +794,11 @@ export class DebugService implements IDebugService { if (editor) { const control = editor.getControl(); if (stackFrame && isCodeEditor(control) && control.hasModel()) { - const lineContent = control.getModel().getLineContent(stackFrame.range.startLineNumber); - aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2} {3}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, lineContent)); + const model = control.getModel(); + if (stackFrame.range.startLineNumber <= model.getLineCount()) { + const lineContent = control.getModel().getLineContent(stackFrame.range.startLineNumber); + aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2} {3}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, lineContent)); + } } } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index da945a8ee9f..ca53f26dbf5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { mixin } from 'vs/base/common/objects'; import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; @@ -22,7 +22,7 @@ import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspac import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { Range } from 'vs/editor/common/core/range'; @@ -68,7 +68,7 @@ export class DebugSession implements IDebugSession { @INotificationService private readonly notificationService: INotificationService, @ISignService private readonly signService: ISignService, @IProductService private readonly productService: IProductService, - @IDebugHelperService private readonly debugUIService: IDebugHelperService + @IWindowsService private readonly windowsService: IWindowsService ) { this.id = generateUuid(); this.repl = new ReplModel(this); @@ -169,7 +169,7 @@ export class DebugSession implements IDebugSession { return dbgr.createDebugAdapter(this).then(debugAdapter => { - this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.signService, this.debugUIService); + this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.signService, this.windowsService); return this.raw!.start().then(() => { @@ -577,7 +577,9 @@ export class DebugSession implements IDebugSession { } rawUpdate(data: IRawModelUpdate): void { + const threadIds: number[] = []; data.threads.forEach(thread => { + threadIds.push(thread.id); if (!this.threads.has(thread.id)) { // A new thread came in, initialize it. this.threads.set(thread.id, new Thread(this, thread.name, thread.id)); @@ -589,6 +591,12 @@ export class DebugSession implements IDebugSession { } } }); + this.threads.forEach(t => { + // Remove all old threads which are no longer part of the update #75980 + if (threadIds.indexOf(t.threadId) === -1) { + this.threads.delete(t.threadId); + } + }); const stoppedDetails = data.stoppedDetails; if (stoppedDetails) { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 3eb52275d43..1fdbad5a8f9 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -9,10 +9,29 @@ import * as objects from 'vs/base/common/objects'; import { Action } from 'vs/base/common/actions'; import * as errors from 'vs/base/common/errors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { formatPII } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger, IDebugHelperService, ILaunchVSCodeArguments } from 'vs/workbench/contrib/debug/common/debug'; +import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { URI } from 'vs/base/common/uri'; +import { IProcessEnvironment } from 'vs/base/common/platform'; + +/** + * This interface represents a single command line argument split into a "prefix" and a "path" half. + * The optional "prefix" contains arbitrary text and the optional "path" contains a file system path. + * Concatenating both results in the original command line argument. + */ +interface ILaunchVSCodeArgument { + prefix?: string; + path?: string; +} + +interface ILaunchVSCodeArguments { + args: ILaunchVSCodeArgument[]; + env?: { [key: string]: string | null; }; +} /** * Encapsulates the DebugAdapter lifecycle and some idiosyncrasies of the Debug Adapter Protocol. @@ -56,7 +75,8 @@ export class RawDebugSession { private readonly telemetryService: ITelemetryService, public readonly customTelemetryService: ITelemetryService | undefined, private readonly signService: ISignService, - private readonly debugUIService: IDebugHelperService + private readonly windowsService: IWindowsService + ) { this.debugAdapter = debugAdapter; this._capabilities = Object.create(null); @@ -503,9 +523,9 @@ export class RawDebugSession { switch (request.command) { case 'launchVSCode': - this.debugUIService.launchVsCode(request.arguments).then(pid => { + this.launchVsCode(request.arguments).then(_ => { response.body = { - processId: pid + //processId: pid }; safeSendResponse(response); }, err => { @@ -549,6 +569,55 @@ export class RawDebugSession { } } + private launchVsCode(vscodeArgs: ILaunchVSCodeArguments): Promise { + + let args: ParsedArgs = { + _: [] + }; + + for (let arg of vscodeArgs.args) { + if (arg.prefix) { + const a2 = (arg.prefix || '') + (arg.path || ''); + const match = /^--(.+)=(.+)$/.exec(a2); + if (match && match.length === 3) { + const key = match[1]; + let value = match[2]; + + if ((key === 'file-uri' || key === 'folder-uri') && !isUri(arg.path)) { + value = URI.file(value).toString(); + + const v = args[key]; + if (v) { + if (Array.isArray(v)) { + v.push(value); + } else { + args[key] = [v, value]; + } + } else { + args[key] = value; + } + + } else { + args[key] = value; + } + + } else { + args._.push(a2); + } + } + } + + let env: IProcessEnvironment = {}; + if (vscodeArgs.env) { + // merge environment variables into a copy of the process.env + env = objects.mixin(objects.mixin(env, process.env), vscodeArgs.env); + // and delete some if necessary + Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]); + } + + return this.windowsService.openExtensionDevelopmentHostWindow(args, env); + } + private send(command: string, args: any, timeout?: number): Promise { return new Promise((completeDispatch, errorDispatch) => { if (!this.debugAdapter) { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index ea36e337eaf..a2c6a6859ba 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -62,7 +62,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { VariablesRenderer, variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView'; const $ = dom.$; @@ -274,6 +274,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati revealLastElement(this.tree); this.history.add(this.replInput.getValue()); this.replInput.setValue(''); + variableSetEmitter.fire(); const shouldRelayout = this.replInputHeight > Repl.REPL_INPUT_INITIAL_HEIGHT; this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; if (shouldRelayout) { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index ccb7850984b..c40b288dec9 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -30,6 +30,7 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; const $ = dom.$; +let forgetScopes = true; export const variableSetEmitter = new Emitter(); @@ -99,7 +100,14 @@ export class VariablesView extends ViewletPanel { const timeout = sf.explicit ? 0 : undefined; this.onFocusStackFrameScheduler.schedule(timeout); })); - this._register(variableSetEmitter.event(() => this.tree.updateChildren())); + this._register(variableSetEmitter.event(() => { + const stackFrame = this.debugService.getViewModel().focusedStackFrame; + if (stackFrame && forgetScopes) { + stackFrame.forgetScopes(); + } + forgetScopes = true; + this.tree.updateChildren(); + })); this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); @@ -256,7 +264,11 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { if (success && variable.value !== value) { variable.setVariable(value) // Need to force watch expressions and variables to update since a variable change can have an effect on both - .then(() => variableSetEmitter.fire()); + .then(() => { + // Do not refresh scopes due to a node limitation #15520 + forgetScopes = false; + variableSetEmitter.fire(); + }); } } }; diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index a76a133ba64..53549128538 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -247,6 +247,7 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { onFinish: (value: string, success: boolean) => { if (success && value) { this.debugService.renameWatchExpression(expression.getId(), value); + variableSetEmitter.fire(); } else if (!expression.name) { this.debugService.removeWatchExpressions(expression.getId()); } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index de7ba55bf0f..f36d6529d77 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -301,6 +301,7 @@ export interface IStackFrame extends ITreeElement { getScopes(): Promise; getMostSpecificScopes(range: IRange): Promise>; getSpecificSourceName(): string; + forgetScopes(): void; restart(): Promise; toString(): string; openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; @@ -842,27 +843,10 @@ export interface IDebugEditorContribution extends IEditorContribution { export const DEBUG_HELPER_SERVICE_ID = 'debugHelperService'; export const IDebugHelperService = createDecorator(DEBUG_HELPER_SERVICE_ID); -/** - * This interface represents a single command line argument split into a "prefix" and a "path" half. - * The optional "prefix" contains arbitrary text and the optional "path" contains a file system path. - * Concatenating both results in the original command line argument. - */ -export interface ILaunchVSCodeArgument { - prefix?: string; - path?: string; -} - -export interface ILaunchVSCodeArguments { - args: ILaunchVSCodeArgument[]; - env?: { [key: string]: string | null; }; -} - export interface IDebugHelperService { _serviceBrand: any; createTerminalLauncher(instantiationService: IInstantiationService): ITerminalLauncher; - launchVsCode(vscodeArgs: ILaunchVSCodeArguments): Promise; - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined; } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index f446574a22d..598c3b95a17 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -124,7 +124,7 @@ export class ExpressionContainer implements IExpressionContainer { return this.children; } - private doGetChildren(): Promise { + private async doGetChildren(): Promise { if (!this.hasChildren) { return Promise.resolve([]); } @@ -134,29 +134,28 @@ export class ExpressionContainer implements IExpressionContainer { } // Check if object has named variables, fetch them independent from indexed variables #9670 - const childrenThenable = !!this.namedVariables ? this.fetchVariables(undefined, undefined, 'named') : Promise.resolve([]); - return childrenThenable.then(childrenArray => { - // Use a dynamic chunk size based on the number of elements #9774 - let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE; - while (!!this.indexedVariables && this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) { - chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE; + const children = this.namedVariables ? await this.fetchVariables(undefined, undefined, 'named') : []; + + // Use a dynamic chunk size based on the number of elements #9774 + let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE; + while (!!this.indexedVariables && this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) { + chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE; + } + + if (!!this.indexedVariables && this.indexedVariables > chunkSize) { + // There are a lot of children, create fake intermediate values that represent chunks #9537 + const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize); + for (let i = 0; i < numberOfChunks; i++) { + const start = (this.startOfVariables || 0) + i * chunkSize; + const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize); + children.push(new Variable(this.session, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, { kind: 'virtual' }, undefined, true, start)); } - if (!!this.indexedVariables && this.indexedVariables > chunkSize) { - // There are a lot of children, create fake intermediate values that represent chunks #9537 - const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize); - for (let i = 0; i < numberOfChunks; i++) { - const start = (this.startOfVariables || 0) + i * chunkSize; - const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize); - childrenArray.push(new Variable(this.session, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, { kind: 'virtual' }, undefined, true, start)); - } + return children; + } - return childrenArray; - } - - return this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed') - .then(variables => childrenArray.concat(variables)); - }); + const variables = await this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed'); + return children.concat(variables); } getId(): string { @@ -214,7 +213,7 @@ export class Expression extends ExpressionContainer implements IExpression { } } - evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string): Promise { + async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string): Promise { if (!session || (!stackFrame && context !== 'repl')) { this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions") : Expression.DEFAULT_VALUE; this.available = false; @@ -224,7 +223,8 @@ export class Expression extends ExpressionContainer implements IExpression { } this.session = session; - return session.evaluate(this.name, stackFrame ? stackFrame.frameId : undefined, context).then(response => { + try { + const response = await session.evaluate(this.name, stackFrame ? stackFrame.frameId : undefined, context); this.available = !!(response && response.body); if (response && response.body) { this.value = response.body.result; @@ -233,11 +233,11 @@ export class Expression extends ExpressionContainer implements IExpression { this.indexedVariables = response.body.indexedVariables; this.type = response.body.type || this.type; } - }, err => { - this.value = err.message; + } catch (e) { + this.value = e.message; this.available = false; this.reference = 0; - }); + } } toString(): string { @@ -268,12 +268,13 @@ export class Variable extends ExpressionContainer implements IExpression { this.value = value; } - setVariable(value: string): Promise { + async setVariable(value: string): Promise { if (!this.session) { return Promise.resolve(undefined); } - return this.session.setVariable((this.parent).reference, this.name, value).then(response => { + try { + const response = await this.session.setVariable((this.parent).reference, this.name, value); if (response && response.body) { this.value = response.body.value; this.type = response.body.type || this.type; @@ -281,9 +282,9 @@ export class Variable extends ExpressionContainer implements IExpression { this.namedVariables = response.body.namedVariables; this.indexedVariables = response.body.indexedVariables; } - }, err => { + } catch (err) { this.errorMessage = err.message; - }); + } } toString(): string { @@ -313,7 +314,7 @@ export class Scope extends ExpressionContainer implements IScope { export class StackFrame implements IStackFrame { - private scopes: Promise | null; + private scopes: Promise | undefined; constructor( public thread: IThread, @@ -323,9 +324,7 @@ export class StackFrame implements IStackFrame { public presentationHint: string | undefined, public range: IRange, private index: number - ) { - this.scopes = null; - } + ) { } getId(): string { return `stackframe:${this.thread.getId()}:${this.frameId}:${this.index}`; @@ -381,6 +380,10 @@ export class StackFrame implements IStackFrame { return this.thread.session.restartFrame(this.frameId, this.thread.threadId); } + forgetScopes(): void { + this.scopes = undefined; + } + toString(): string { const lineNumberToString = typeof this.range.startLineNumber === 'number' ? `:${this.range.startLineNumber}` : ''; const sourceToString = `${this.source.inMemory ? this.source.name : this.source.uri.fsPath}${lineNumberToString}`; diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index 9cd391a66f6..6eaa73a2838 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -17,8 +17,9 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE jsonSchema: { description: nls.localize('vscode.extension.contributes.debuggers', 'Contributes debug adapters.'), type: 'array', - defaultSnippets: [{ body: [{ type: '', extensions: [] }] }], + defaultSnippets: [{ body: [{ type: '' }] }], items: { + additionalProperties: false, type: 'object', defaultSnippets: [{ body: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [''] } } }], properties: { @@ -118,6 +119,7 @@ export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registe defaultSnippets: [{ body: [{ language: '' }] }], items: { type: 'object', + additionalProperties: false, defaultSnippets: [{ body: { language: '' } }], properties: { language: { diff --git a/src/vs/workbench/contrib/debug/node/debugHelperService.ts b/src/vs/workbench/contrib/debug/node/debugHelperService.ts index cfbc3539189..76fcf6e30e8 100644 --- a/src/vs/workbench/contrib/debug/node/debugHelperService.ts +++ b/src/vs/workbench/contrib/debug/node/debugHelperService.ts @@ -3,13 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as objects from 'vs/base/common/objects'; -import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; -import * as cp from 'child_process'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalLauncher } from 'vs/workbench/contrib/debug/node/terminalSupport'; -import { ITerminalLauncher, IDebugHelperService, ILaunchVSCodeArguments } from 'vs/workbench/contrib/debug/common/debug'; +import { ITerminalLauncher, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; import { Client as TelemetryClient } from 'vs/base/parts/ipc/node/ipc.cp'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -21,7 +17,6 @@ export class NodeDebugHelperService implements IDebugHelperService { _serviceBrand: any; constructor( - @IEnvironmentService private readonly environmentService: IEnvironmentService ) { } @@ -29,82 +24,6 @@ export class NodeDebugHelperService implements IDebugHelperService { return instantiationService.createInstance(TerminalLauncher); } - launchVsCode(vscodeArgs: ILaunchVSCodeArguments): Promise { - - const spawnOpts: cp.SpawnOptions = { - detached: false // https://github.com/Microsoft/vscode/issues/57018 - }; - - if (vscodeArgs.env) { - // merge environment variables into a copy of the process.env - const envArgs = objects.mixin(objects.mixin({}, process.env), vscodeArgs.env); - // and delete some if necessary - Object.keys(envArgs).filter(k => envArgs[k] === null).forEach(key => delete envArgs[key]); - spawnOpts.env = envArgs; - } - - let spawnArgs = vscodeArgs.args.map(a => { - if ((a.prefix === '--file-uri=' || a.prefix === '--folder-uri=') && !isUri(a.path)) { - return (a.path || ''); - } - return (a.prefix || '') + (a.path || ''); - }); - - let runtimeExecutable = this.environmentService['execPath']; - if (!runtimeExecutable) { - return Promise.reject(new Error(`VS Code executable unknown`)); - } - - // if VS Code runs out of sources, add the VS Code workspace path as the first argument so that Electron turns into VS Code - const electronIdx = runtimeExecutable.indexOf(process.platform === 'win32' ? '\\.build\\electron\\' : '/.build/electron/'); - if (electronIdx > 0) { - // guess the VS Code workspace path from the executable - const vscodeWorkspacePath = runtimeExecutable.substr(0, electronIdx); - - // only add VS Code workspace path if user hasn't already added that path as a (folder) argument - const x = spawnArgs.filter(a => a.indexOf(vscodeWorkspacePath) === 0); - if (x.length === 0) { - spawnArgs.unshift(vscodeWorkspacePath); - } - } - - // Workaround for bug Microsoft/vscode#45832 - if (process.platform === 'win32' && runtimeExecutable.indexOf(' ') > 0) { - let foundArgWithSpace = false; - - // check whether there is one arg with a space - const args: string[] = []; - for (const a of spawnArgs) { - if (a.indexOf(' ') > 0) { - args.push(`"${a}"`); - foundArgWithSpace = true; - } else { - args.push(a); - } - } - - if (foundArgWithSpace) { - spawnArgs = args; - runtimeExecutable = `"${runtimeExecutable}"`; - spawnOpts.shell = true; - } - } - - return new Promise((resolve, reject) => { - const process = cp.spawn(runtimeExecutable, spawnArgs, spawnOpts); - process.on('error', error => { - reject(error); - }); - process.on('exit', code => { - if (code === 0) { - resolve(process.pid); - } else { - reject(new Error(`VS Code exited with ${code}`)); - } - }); - }); - } - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { const client = new TelemetryClient( diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 300c738f0eb..164dd27a15a 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -10,7 +10,7 @@ import * as pfs from 'vs/base/node/pfs'; import { assign } from 'vs/base/common/objects'; import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; +import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); @@ -315,13 +315,13 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments let shell: string; const shell_config = config.integrated.shell; if (env.isWindows) { - shell = shell_config.windows || getDefaultShell(env.Platform.Windows); + shell = shell_config.windows || getSystemShell(env.Platform.Windows); shellType = ShellType.cmd; } else if (env.isLinux) { - shell = shell_config.linux || getDefaultShell(env.Platform.Linux); + shell = shell_config.linux || getSystemShell(env.Platform.Linux); shellType = ShellType.bash; } else if (env.isMacintosh) { - shell = shell_config.osx || getDefaultShell(env.Platform.Mac); + shell = shell_config.osx || getSystemShell(env.Platform.Mac); shellType = ShellType.bash; } else { throw new Error('Unknown platform'); diff --git a/src/vs/workbench/contrib/experiments/node/experimentService.ts b/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts similarity index 99% rename from src/vs/workbench/contrib/experiments/node/experimentService.ts rename to src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts index b20c247e95a..3a451dc20bd 100644 --- a/src/vs/workbench/contrib/experiments/node/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts @@ -19,7 +19,7 @@ import { match } from 'vs/base/common/glob'; import { asJson } from 'vs/base/node/request'; import { Emitter, Event } from 'vs/base/common/event'; import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; -import { WorkspaceStats } from 'vs/workbench/contrib/stats/node/workspaceStats'; +import { WorkspaceStats } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; import { CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { lastSessionDateStorageKey } from 'vs/platform/telemetry/node/workbenchCommonProperties'; diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts b/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts index 29d91beb05e..3581351f6e0 100644 --- a/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts +++ b/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts @@ -5,7 +5,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; -import { IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties, IExperimentActionPromptCommand, ExperimentState } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties, IExperimentActionPromptCommand, ExperimentState } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts b/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts index b11ad64d0ce..f3dcaf5074e 100644 --- a/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts +++ b/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 69f2ac4cc51..d98057ee111 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ExperimentService, ExperimentActionType, ExperimentState, IExperiment } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { ExperimentService, ExperimentActionType, ExperimentState, IExperiment } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index 16bfb425613..bd21a63a6a2 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -13,7 +13,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt'; -import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index ae1ffe0c808..e0e9a941d9c 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -130,7 +130,7 @@ export class ExtensionContainers extends Disposable { for (const container of this.containers) { if (extension && container.extension) { if (areSameExtensions(container.extension.identifier, extension.identifier)) { - if (!container.extension.server || container.extension.server === extension.server) { + if (!container.extension.server || !extension.server || container.extension.server === extension.server) { container.extension = extension; } else if (container.updateWhenCounterExtensionChanges) { container.update(); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index e16509f850d..efa09426215 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts @@ -31,7 +31,7 @@ import { flatten, distinct, shuffle, coalesce } from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { getHashedRemotesFromUri } from 'vs/workbench/contrib/stats/node/workspaceStats'; +import { getHashedRemotesFromUri } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; import { IRequestService } from 'vs/platform/request/node/request'; import { asJson } from 'vs/base/node/request'; import { isNumber } from 'vs/base/common/types'; @@ -41,7 +41,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { extname } from 'vs/base/common/resources'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 7a7282a0c42..fe4f91ede5c 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -68,7 +68,8 @@ function toExtensionDescription(local: ILocalExtension): IExtensionDescription { isBuiltin: local.type === ExtensionType.System, isUnderDevelopment: false, extensionLocation: local.location, - ...local.manifest + ...local.manifest, + uuid: local.identifier.uuid }; } @@ -217,7 +218,7 @@ export class InstallAction extends ExtensionAction { alert(localize('installExtensionComplete', "Installing extension {0} is completed. Please reload Visual Studio Code to enable it.", this.extension.displayName)); - if (extension.local) { + if (extension && extension.local) { const runningExtension = await this.getRunningExtension(extension.local); if (runningExtension) { const colorThemes = await this.workbenchThemeService.getColorThemes(); @@ -237,7 +238,7 @@ export class InstallAction extends ExtensionAction { } - private install(extension: IExtension): Promise { + private install(extension: IExtension): Promise { return this.extensionsWorkbenchService.install(extension) .then(null, err => { if (!extension.gallery) { @@ -948,7 +949,7 @@ export class DisableForWorkspaceAction extends ExtensionAction { update(): void { this.enabled = false; - if (this.extension && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { + if (this.extension && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.Enabled || this.extension.enablementState === EnablementState.WorkspaceEnabled) && !!this.extension.local && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } @@ -973,7 +974,7 @@ export class DisableGloballyAction extends ExtensionAction { update(): void { this.enabled = false; - if (this.extension && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))) { + if (this.extension && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.Enabled || this.extension.enablementState === EnablementState.WorkspaceEnabled) && !!this.extension.local && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } @@ -1241,7 +1242,7 @@ export class ReloadAction extends ExtensionAction { return; } const isUninstalled = this.extension.state === ExtensionState.Uninstalled; - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0]; + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; const isSameExtensionRunning = runningExtension && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); if (isUninstalled) { @@ -2492,7 +2493,7 @@ export class StatusLabelAction extends Action implements IExtensionContainer { const runningExtensions = await this.extensionService.getExtensions(); const canAddExtension = () => { - const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0]; + const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; if (this.extension.local) { if (runningExtension && this.extension.version === runningExtension.version) { return true; @@ -2503,7 +2504,7 @@ export class StatusLabelAction extends Action implements IExtensionContainer { }; const canRemoveExtension = () => { if (this.extension.local) { - if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value }, this.extension.identifier) && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { + if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier) && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { return true; } return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension.local)); @@ -2601,7 +2602,7 @@ export class DisabledLabelAction extends ExtensionAction { } if (this.extension && this.extension.local && this._runningExtensions) { const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); - const isExtensionRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier)); + const isExtensionRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier)); if (!isExtensionRunning && !isEnabled && this.extensionEnablementService.canChangeEnablement(this.extension.local)) { this.class = DisabledLabelAction.Class; this.label = localize('disabled by user', "This extension is disabled by the user."); @@ -2667,7 +2668,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { } return; } - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0]; + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; const localExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; const localExtensionServer = localExtension ? localExtension.server : null; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts index 585a81141ec..3f6427704d9 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts @@ -154,7 +154,7 @@ export class Renderer implements IPagedRenderer { const updateEnablement = async () => { const runningExtensions = await this.extensionService.getExtensions(); if (extension.local && !isLanguagePackExtension(extension.local.manifest)) { - const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, extension.identifier))[0]; + const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0]; const isSameExtensionRunning = runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); toggleClass(data.root, 'disabled', !isSameExtensionRunning); } else { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts index e5f177c88b1..6dad2fb5808 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts @@ -34,7 +34,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { distinct, coalesce } from 'vs/base/common/arrays'; -import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; @@ -392,7 +392,7 @@ export class ExtensionsListView extends ViewletPanel { const result = local .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) - .filter(e => runningExtensions.every(r => !areSameExtensions({ id: r.identifier.value }, e.identifier)) + .filter(e => runningExtensions.every(r => !areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier)) && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); @@ -407,7 +407,7 @@ export class ExtensionsListView extends ViewletPanel { const result = local .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) - .filter(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value }, e.identifier)) + .filter(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier)) && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); diff --git a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts index fd88186467d..c08a6e37c16 100644 --- a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts @@ -309,13 +309,13 @@ class Extensions extends Disposable { private readonly _onChange: Emitter = new Emitter(); get onChange(): Event { return this._onChange.event; } - private readonly stateProvider: IExtensionStateProvider; private installing: Extension[] = []; private uninstalling: Extension[] = []; private installed: Extension[] = []; constructor( private readonly server: IExtensionManagementServer, + private readonly stateProvider: IExtensionStateProvider, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, @@ -323,7 +323,6 @@ class Extensions extends Disposable { @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService ) { super(); - this.stateProvider = ext => this.getExtensionState(ext); this._register(server.extensionManagementService.onInstallExtension(e => this.onInstallExtension(e))); this._register(server.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e))); this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e))); @@ -341,7 +340,7 @@ class Extensions extends Disposable { async queryInstalled(): Promise { const installed = await this.server.extensionManagementService.getInstalled(); - const byId = index(this.installed, e => e.identifier.id); + const byId = index(this.installed, e => e.local ? e.local.identifier.id : e.identifier.id); this.installed = installed.map(local => { const extension = byId[local.identifier.id] || new Extension(this.galleryService, this.stateProvider, this.server, local, undefined, this.telemetryService, this.logService, this.fileService); extension.local = local; @@ -484,6 +483,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension get onChange(): Event { return this._onChange.event; } private _extensionAllowedBadgeProviders: string[]; + private installing: IExtension[] = []; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -504,10 +504,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IModeService private readonly modeService: IModeService ) { super(); - this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer)); + this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.localExtensions.onChange(e => this._onChange.fire(e))); if (this.extensionManagementServerService.remoteExtensionManagementServer) { - this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer)); + this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.remoteExtensions.onChange(e => this._onChange.fire(e))); } else { this.remoteExtensions = null; @@ -672,6 +672,13 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } private getExtensionState(extension: Extension): ExtensionState { + const isInstalling = this.installing.some(i => areSameExtensions(i.identifier, extension.identifier)); + if (extension.server) { + const state = (extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.localExtensions : this.remoteExtensions!).getExtensionState(extension); + return state === ExtensionState.Uninstalled && isInstalling ? ExtensionState.Installing : state; + } else if (isInstalling) { + return ExtensionState.Installing; + } if (this.remoteExtensions) { const state = this.remoteExtensions.getExtensionState(extension); if (state !== ExtensionState.Uninstalled) { @@ -754,9 +761,9 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension install(extension: string | IExtension): Promise { if (typeof extension === 'string') { return this.installWithProgress(async () => { - const extensionIdentifier = await this.extensionService.install(URI.file(extension)); - this.checkAndEnableDisabledDependencies(extensionIdentifier); - return this.local.filter(local => areSameExtensions(local.identifier, extensionIdentifier))[0]; + const { identifier } = await this.extensionService.install(URI.file(extension)); + this.checkAndEnableDisabledDependencies(identifier); + return this.local.filter(local => areSameExtensions(local.identifier, identifier))[0]; }); } @@ -771,8 +778,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } return this.installWithProgress(async () => { - const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService; - await extensionService.installFromGallery(gallery); + await this.installFromGallery(extension, gallery); this.checkAndEnableDisabledDependencies(gallery.identifier); return this.local.filter(local => areSameExtensions(local.identifier, gallery.identifier))[0]; }, gallery.displayName); @@ -790,8 +796,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (!toUninstall) { return Promise.reject(new Error('Missing local')); } - - this.logService.info(`Requested uninstalling the extension ${extension.identifier.id} from window ${this.windowService.windowId}`); return this.progressService.withProgress({ location: ProgressLocation.Extensions, title: nls.localize('uninstallingExtension', 'Uninstalling extension....'), @@ -811,11 +815,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return this.galleryService.getCompatibleExtension(extension.gallery.identifier, version) .then(gallery => { if (!gallery) { - return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' with version '{1}' as it is not compatible with VS Code.", extension.gallery!.identifier.id, version))); + return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.gallery!.identifier.id, version))); } return this.installWithProgress(async () => { - const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService; - await extensionService.installFromGallery(gallery); + await this.installFromGallery(extension, gallery); if (extension.latestVersion !== version) { this.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(gallery.identifier, version)); } @@ -847,6 +850,21 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }, () => installTask()); } + private async installFromGallery(extension: IExtension, gallery: IGalleryExtension): Promise { + this.installing.push(extension); + this._onChange.fire(extension); + try { + const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService; + await extensionService.installFromGallery(gallery); + const ids: string[] | undefined = extension.identifier.uuid ? [extension.identifier.uuid] : undefined; + const names: string[] | undefined = extension.identifier.uuid ? undefined : [extension.identifier.id]; + this.queryGallery({ names, ids, pageSize: 1 }, CancellationToken.None); + } finally { + this.installing = this.installing.filter(e => e !== extension); + this._onChange.fire(this.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]); + } + } + private checkAndEnableDisabledDependencies(extensionIdentifier: IExtensionIdentifier): Promise { const extension = this.local.filter(e => (e.local || e.gallery) && areSameExtensions(extensionIdentifier, e.identifier))[0]; if (extension) { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 59758c4c6c3..9d1919ae5e5 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -40,6 +40,7 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { CancellationToken } from 'vs/base/common/cancellation'; import { ILabelService } from 'vs/platform/label/common/label'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService'; +import { IProductService } from 'vs/platform/product/common/product'; suite('ExtensionsActions Test', () => { @@ -78,7 +79,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { private _localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', authority: 'vscode-local' }; constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService)); + super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(IExtensionGalleryService), instantiationService.get(IConfigurationService), instantiationService.get(IProductService), instantiationService.get(ILogService)); } get localExtensionManagementServer(): IExtensionManagementServer { return this._localExtensionManagementServer; } set localExtensionManagementServer(server: IExtensionManagementServer) { } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 32d569b0b57..90b0616f421 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -41,7 +41,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification'; import { URLService } from 'vs/platform/url/common/urlService'; -import { IExperimentService } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { IExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 8bdce63bf94..c9ad5d996c8 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -34,12 +34,13 @@ import { URLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { SinonStub } from 'sinon'; -import { IExperimentService, ExperimentService, ExperimentState, ExperimentActionType } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { IExperimentService, ExperimentService, ExperimentState, ExperimentActionType } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService'; +import { IProductService } from 'vs/platform/product/common/product'; suite('ExtensionsListView Tests', () => { @@ -93,7 +94,7 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { private _localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', authority: 'vscode-local' }; constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService)); + super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(IExtensionGalleryService), instantiationService.get(IConfigurationService), instantiationService.get(IProductService), instantiationService.get(ILogService)); } get localExtensionManagementServer(): IExtensionManagementServer { return this._localExtensionManagementServer; } set localExtensionManagementServer(server: IExtensionManagementServer) { } diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.contribution.ts b/src/vs/workbench/contrib/feedback/browser/feedback.contribution.ts index 10c044082fb..b6d1fa758f6 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.contribution.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.contribution.ts @@ -4,16 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { IStatusbarRegistry, Extensions, StatusbarItemDescriptor } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; -import { FeedbackStatusbarItem } from 'vs/workbench/contrib/feedback/browser/feedbackStatusbarItem'; -import { localize } from 'vs/nls'; +import { FeedbackStatusbarConribution } from 'vs/workbench/contrib/feedback/browser/feedbackStatusbarItem'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -// Register Statusbar item -Registry.as(Extensions.Statusbar).registerStatusbarItem(new StatusbarItemDescriptor( - FeedbackStatusbarItem, - 'status.feedback', - localize('status.feedback', "Tweet Feedback"), - StatusbarAlignment.RIGHT, - -100 /* towards the end of the right hand side */ -)); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FeedbackStatusbarConribution, LifecyclePhase.Starting); \ No newline at end of file diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index be3390b1d38..033a7763448 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/feedback'; import * as nls from 'vs/nls'; -import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Dropdown } from 'vs/base/browser/ui/dropdown/dropdown'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import * as dom from 'vs/base/browser/dom'; @@ -17,7 +17,6 @@ import { editorWidgetBackground, widgetShadow, inputBorder, inputForeground, inp import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IProductService } from 'vs/platform/product/common/product'; @@ -68,15 +67,7 @@ export class FeedbackDropdown extends Dropdown { @IStatusbarService private readonly statusbarService: IStatusbarService, @IProductService productService: IProductService ) { - super(container, { - contextViewProvider: options.contextViewProvider, - labelRenderer: (container: HTMLElement): IDisposable => { - const label = new OcticonLabel(container); - label.text = '$(smiley)'; - - return Disposable.None; - } - }); + super(container, options); this.feedbackDelegate = options.feedbackService; this.maxFeedbackCharacters = this.feedbackDelegate.getCharacterLimit(this.sentiment); diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 873c58fdf58..43d18373cc3 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -3,16 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; -import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; +import { Disposable } from 'vs/base/common/lifecycle'; import { FeedbackDropdown, IFeedback, IFeedbackDelegate } from 'vs/workbench/contrib/feedback/browser/feedback'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Themable, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { clearNode, EventHelper, addClass, removeClass, addDisposableListener } from 'vs/base/browser/dom'; import { IProductService } from 'vs/platform/product/common/product'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IStatusbarService, StatusbarAlignment, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { localize } from 'vs/nls'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; class TwitterFeedbackService implements IFeedbackDelegate { @@ -47,76 +46,50 @@ class TwitterFeedbackService implements IFeedbackDelegate { } } -export class FeedbackStatusbarItem extends Themable implements IStatusbarItem { - private dropdown: FeedbackDropdown | undefined; - private container: HTMLElement; +export class FeedbackStatusbarConribution extends Disposable implements IWorkbenchContribution { + private dropdown: FeedbackDropdown; + private entry: IStatusbarEntryAccessor; constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IContextViewService private readonly contextViewService: IContextViewService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IThemeService themeService: IThemeService, - @IProductService private productService: IProductService + @IStatusbarService statusbarService: IStatusbarService, + @IProductService productService: IProductService, + @IInstantiationService private instantiationService: IInstantiationService, + @IContextViewService private contextViewService: IContextViewService ) { - super(themeService); + super(); - this.registerListeners(); + if (productService.sendASmile) { + this.entry = this._register(statusbarService.addEntry(this.getStatusEntry(), 'status.feedback', localize('status.feedback', "Tweet Feedback"), StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */)); + + CommandsRegistry.registerCommand('_feedback.open', () => this.toggleFeedback()); + } } - private registerListeners(): void { - this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles())); - } - - render(element: HTMLElement): IDisposable { - this.container = element; - - // Prevent showing dropdown on anything but left click - this._register(addDisposableListener(this.container, 'mousedown', (e: MouseEvent) => { - if (e.button !== 0) { - EventHelper.stop(e, true); - } - }, true)); - - return this.update(); - } - - private update(): IDisposable { - - // Create - if (this.productService.sendASmile) { - if (!this.dropdown) { - this.dropdown = this._register(this.instantiationService.createInstance(FeedbackDropdown, this.container, { + private toggleFeedback(): void { + if (!this.dropdown) { + const statusContainr = document.getElementById('status.feedback'); + if (statusContainr) { + this.dropdown = this._register(this.instantiationService.createInstance(FeedbackDropdown, statusContainr.getElementsByClassName('octicon').item(0), { contextViewProvider: this.contextViewService, feedbackService: this.instantiationService.createInstance(TwitterFeedbackService), - onFeedbackVisibilityChange: (visible: boolean) => { - if (visible) { - addClass(this.container, 'has-beak'); - } else { - removeClass(this.container, 'has-beak'); - } - } + onFeedbackVisibilityChange: visible => this.entry.update(this.getStatusEntry(visible)) })); - - this.updateStyles(); - - return this.dropdown; } } - // Dispose - else { - dispose(this.dropdown); - this.dropdown = undefined; - clearNode(this.container); + if (!this.dropdown.isVisible()) { + this.dropdown.show(); + } else { + this.dropdown.hide(); } - - return Disposable.None; } -} -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { - const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); - if (statusBarItemHoverBackground) { - collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item .monaco-dropdown.send-feedback:hover { background-color: ${statusBarItemHoverBackground}; }`); + private getStatusEntry(showBeak?: boolean): IStatusbarEntry { + return { + text: '$(smiley)', + command: '_feedback.open', + showBeak + }; } -}); + +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/feedback/browser/media/feedback.css b/src/vs/workbench/contrib/feedback/browser/media/feedback.css index 8ab8d13c6ff..3520a24c06a 100644 --- a/src/vs/workbench/contrib/feedback/browser/media/feedback.css +++ b/src/vs/workbench/contrib/feedback/browser/media/feedback.css @@ -113,17 +113,6 @@ background-color: #eaeaea; } -/* Statusbar */ -.monaco-workbench .statusbar > .items-container > .statusbar-item > .monaco-dropdown.send-feedback { - display: inline-block; - padding: 0 5px 0 5px; -} - -.monaco-workbench .statusbar > .items-container > .statusbar-item > .monaco-dropdown.send-feedback span.octicon { - text-align: center; - font-size: 14px; -} - /* Theming */ .vs .monaco-workbench .feedback-form .feedback-alias, .vs .monaco-workbench .feedback-form .feedback-description { font-family: inherit; diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index f9719911f6c..580a2990ac4 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -16,6 +16,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; /** * An implementation of editor for binary files like images. @@ -32,6 +33,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { @IStorageService storageService: IStorageService, @IFileService fileService: IFileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IInstantiationService instantiationService: IInstantiationService, ) { super( BinaryFileEditor.ID, @@ -44,6 +46,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { fileService, environmentService, storageService, + instantiationService, ); } diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 6a8354106cd..193b9d2e15b 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -14,7 +14,7 @@ import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEA import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { isWindows, isMacintosh, isWeb } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -23,8 +23,9 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { SupportsWorkspacesContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; +import { SupportsWorkspacesContext, IsWebContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { OpenFileFolderAction, OpenLocalFileFolderCommand, OpenFileAction, OpenFolderAction, OpenLocalFileCommand, OpenLocalFolderCommand, OpenWorkspaceAction, SaveLocalFileCommand } from 'vs/workbench/browser/actions/workspaceActions'; // Contribute Global Actions const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; @@ -41,6 +42,58 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ShowOpenedFileInNewWin registry.registerWorkbenchAction(new SyncActionDescriptor(CompareWithClipboardAction, CompareWithClipboardAction.ID, CompareWithClipboardAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_C) }), 'File: Compare Active File with Clipboard', category.value); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleAutoSaveAction, ToggleAutoSaveAction.ID, ToggleAutoSaveAction.LABEL), 'File: Toggle Auto Save', category.value); + +const fileCategory = nls.localize('file', "File"); + +if (isMacintosh) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); + if (!isWeb) { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: OpenLocalFileFolderCommand.ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KEY_O, + when: RemoteFileDialogContext, + description: { description: OpenLocalFileFolderCommand.LABEL, args: [] }, + handler: OpenLocalFileFolderCommand.handler() + }); + } +} else { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); + if (!isWeb) { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: OpenLocalFileCommand.ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KEY_O, + when: RemoteFileDialogContext, + description: { description: OpenLocalFileCommand.LABEL, args: [] }, + handler: OpenLocalFileCommand.handler() + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: OpenLocalFolderCommand.ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O), + when: RemoteFileDialogContext, + description: { description: OpenLocalFolderCommand.LABEL, args: [] }, + handler: OpenLocalFolderCommand.handler() + }); + } +} + +if (!isWeb) { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SaveLocalFileCommand.ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, + when: RemoteFileDialogContext, + description: { description: SaveLocalFileCommand.LABEL, args: [] }, + handler: SaveLocalFileCommand.handler() + }); +} + +const workspacesCategory = nls.localize('workspaces', "Workspaces"); +registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, SupportsWorkspacesContext); + // Commands CommandsRegistry.registerCommand('_files.windowOpen', openWindowCommand); CommandsRegistry.registerCommand('_files.newWindow', newWindowCommand); @@ -586,6 +639,45 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { order: 3 }); +if (isMacintosh) { + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenFileFolderAction.ID, + title: nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...") + }, + order: 1 + }); +} else { + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenFileAction.ID, + title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenFolderAction.ID, + title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") + }, + order: 2 + }); +} + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenWorkspaceAction.ID, + title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") + }, + order: 3, + when: SupportsWorkspacesContext +}); + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '5_autosave', command: { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index e65deb03e95..f73012495b7 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -36,7 +36,7 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c import { IListService, ListWidget } from 'vs/platform/list/browser/listService'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Schemas } from 'vs/base/common/network'; -import { IDialogService, IConfirmationResult, getConfirmMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IConfirmationResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Constants } from 'vs/editor/common/core/uint'; @@ -916,7 +916,9 @@ export const renameHandler = (accessor: ServicesAccessor) => { if (success) { const parentResource = stat.parent!.resource; const targetResource = resources.joinPath(parentResource, value); - textFileService.move(stat.resource, targetResource).then(() => refreshIfSeparator(value, explorerService), onUnexpectedError); + if (stat.resource.toString() !== targetResource.toString()) { + textFileService.move(stat.resource, targetResource).then(() => refreshIfSeparator(value, explorerService), onUnexpectedError); + } } explorerService.setEditable(stat, null); } @@ -982,18 +984,11 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { } const explorerContext = getContext(listService.lastFocusedList); const textFileService = accessor.get(ITextFileService); - const fileDialogService = accessor.get(IFileDialogService); if (explorerContext.stat) { const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; stats.forEach(async s => { - const resource = await fileDialogService.showSaveDialog({ - availableFileSystems: [Schemas.file], - defaultFileName: basename(s.resource.path) - }); - if (resource) { - await textFileService.saveAs(s.resource, resource); - } + await textFileService.saveAs(s.resource, undefined, { availableFileSystems: [Schemas.file] }); }); } }; diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index f76f558041f..88ad0027e9c 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -311,7 +311,7 @@ configurationRegistry.registerConfiguration({ 'type': 'string', 'scope': ConfigurationScope.APPLICATION, 'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE], - 'default': HotExitConfiguration.ON_EXIT, + 'default': platform.isWeb ? HotExitConfiguration.OFF : HotExitConfiguration.ON_EXIT, // TODO@Ben enable once supported 'markdownEnumDescriptions': [ nls.localize('hotExit.off', 'Disable hot exit.'), nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows with backups will be restored upon next launch.'), diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index dc40cbe11c2..cad0e825ff0 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -21,6 +21,9 @@ import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/v import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { Schemas } from 'vs/base/common/network'; export class EmptyView extends ViewletPanel { @@ -38,10 +41,13 @@ export class EmptyView extends ViewletPanel { @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + @ILabelService private labelService: ILabelService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService); - this.contextService.onDidChangeWorkbenchState(() => this.setLabels()); + this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels())); + this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); } renderHeader(container: HTMLElement): void { @@ -117,7 +123,12 @@ export class EmptyView extends ViewletPanel { } this.titleElement.textContent = EmptyView.NAME; } else { - this.messageElement.textContent = nls.localize('noFolderHelp', "You have not yet opened a folder."); + if (this.environmentService.configuration.remoteAuthority) { + const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.configuration.remoteAuthority); + this.messageElement.textContent = hostLabel ? nls.localize('remoteNoFolderHelp', "Connected to {0}", hostLabel) : nls.localize('connecting', "Connecting..."); + } else { + this.messageElement.textContent = nls.localize('noFolderHelp', "You have not yet opened a folder."); + } if (this.button) { this.button.label = nls.localize('openFolder', "Open Folder"); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 76fab053275..4592b3918ec 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -218,22 +218,29 @@ export class FilesRenderer implements ITreeRenderer 0 && !stat.isDirectory ? lastDot : value.length }); - const done = once(async (success: boolean, finishEditing: boolean) => { + let isFinishableDisposeEvent = false; + setTimeout(() => { + // Check if disposed + if (!inputBox.inputElement) { + return; + } + inputBox.focus(); + inputBox.select({ start: 0, end: lastDot > 0 && !stat.isDirectory ? lastDot : value.length }); + isFinishableDisposeEvent = true; + }, 0); + + const done = once(async (success: boolean) => { label.element.style.display = 'none'; const value = inputBox.value; dispose(toDispose); - container.removeChild(label.element); - if (finishEditing) { - // Timeout: once done rendering only then re-render #70902 - setTimeout(() => editableData.onFinish(value, success), 0); - } + label.element.remove(); + // Timeout: once done rendering only then re-render #70902 + setTimeout(() => editableData.onFinish(value, success), 0); }); const blurDisposable = DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { - done(inputBox.isInputValid(), true); + done(inputBox.isInputValid()); }); const toDispose = [ @@ -241,10 +248,10 @@ export class FilesRenderer implements ITreeRenderer { if (e.equals(KeyCode.Enter)) { if (inputBox.validate()) { - done(true, true); + done(true); } } else if (e.equals(KeyCode.Escape)) { - done(false, true); + done(false); } }), blurDisposable, @@ -253,8 +260,13 @@ export class FilesRenderer implements ITreeRenderer { - blurDisposable.dispose(); - done(false, false); + if (isFinishableDisposeEvent) { + done(false); + } + else { + dispose(toDispose); + label.element.remove(); + } }); } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 494351a4274..cb6d96bd1ff 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -102,6 +102,7 @@ export class ExplorerService implements IExplorerService { this.disposables.push(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { if (e.added && this.fileSystemProviderSchemes.has(e.scheme)) { // A file system provider got re-registered, we should update all file stats since they might change (got read-only) + this.model.roots.forEach(r => r.forgetChildren()); this._onDidChangeItem.fire({ recursive: true }); } else { this.fileSystemProviderSchemes.add(e.scheme); diff --git a/src/vs/workbench/contrib/label/common/label.contribution.ts b/src/vs/workbench/contrib/label/common/label.contribution.ts deleted file mode 100644 index 08308a541fc..00000000000 --- a/src/vs/workbench/contrib/label/common/label.contribution.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { isWeb } from 'vs/base/common/platform'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { Schemas } from 'vs/base/common/network'; - -export class LabelContribution implements IWorkbenchContribution { - constructor( - @ILabelService private readonly labelService: ILabelService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService) { - this.registerFormatters(); - } - - private useWindowsPaths(): boolean { - if (this.environmentService.configuration.folderUri) { - return this.environmentService.configuration.folderUri.fsPath.indexOf('/') === -1; - } - - if (this.environmentService.configuration.workspace) { - return this.environmentService.configuration.workspace.configPath.fsPath.indexOf('/') === -1; - } - - return false; - } - - private registerFormatters(): void { - if (isWeb) { - this.labelService.registerFormatter({ - scheme: Schemas.vscodeRemote, - authority: this.environmentService.configuration.remoteAuthority, - formatting: { - label: '${path}', - separator: this.useWindowsPaths() ? '\\' : '/', - tildify: !this.useWindowsPaths() - } - }); - } - } -} - -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(LabelContribution, LifecyclePhase.Starting); \ No newline at end of file diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index bd1f79c57a4..fa261bc537b 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -4,66 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { OpenLogsFolderAction, SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; -import { dirname } from 'vs/base/common/resources'; +import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; -class LogOutputChannels extends Disposable implements IWorkbenchContribution { - - constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @ILogService logService: ILogService, - @IFileService private readonly fileService: IFileService - ) { - super(); - this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(environmentService.logsPath, `main.log`))); - this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(environmentService.logsPath, `sharedprocess.log`))); - this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), URI.file(join(environmentService.logsPath, `renderer${environmentService.configuration.windowId}.log`))); - - const registerTelemetryChannel = (level: LogLevel) => { - if (level === LogLevel.Trace && !Registry.as(OutputExt.OutputChannels).getChannel(Constants.telemetryLogChannelId)) { - this.registerLogChannel(Constants.telemetryLogChannelId, nls.localize('telemetryLog', "Telemetry"), URI.file(join(environmentService.logsPath, `telemetry.log`))); - } - }; - registerTelemetryChannel(logService.getLevel()); - logService.onDidChangeLogLevel(registerTelemetryChannel); - - const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); - const devCategory = nls.localize('developer', "Developer"); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory); - } - - private async registerLogChannel(id: string, label: string, file: URI): Promise { - const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - const exists = await this.fileService.exists(file); - if (exists) { - outputChannelRegistry.registerChannel({ id, label, file, log: true }); - return; - } - - const watcher = this.fileService.watch(dirname(file)); - const disposable = this.fileService.onFileChanges(e => { - if (e.contains(file, FileChangeType.ADDED)) { - watcher.dispose(); - disposable.dispose(); - outputChannelRegistry.registerChannel({ id, label, file, log: true }); - } - }); - } - -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restored); \ No newline at end of file +const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); +const devCategory = nls.localize('developer', "Developer"); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory); \ No newline at end of file diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts new file mode 100644 index 00000000000..d8d381fa525 --- /dev/null +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { join } from 'vs/base/common/path'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { OpenLogsFolderAction } from 'vs/workbench/contrib/logs/common/logsActions'; +import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; +import { dirname } from 'vs/base/common/resources'; + +class LogOutputChannels extends Disposable implements IWorkbenchContribution { + + constructor( + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ILogService logService: ILogService, + @IFileService private readonly fileService: IFileService + ) { + super(); + this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(environmentService.logsPath, `main.log`))); + this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(environmentService.logsPath, `sharedprocess.log`))); + this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), URI.file(join(environmentService.logsPath, `renderer${environmentService.configuration.windowId}.log`))); + + const registerTelemetryChannel = (level: LogLevel) => { + if (level === LogLevel.Trace && !Registry.as(OutputExt.OutputChannels).getChannel(Constants.telemetryLogChannelId)) { + this.registerLogChannel(Constants.telemetryLogChannelId, nls.localize('telemetryLog', "Telemetry"), URI.file(join(environmentService.logsPath, `telemetry.log`))); + } + }; + registerTelemetryChannel(logService.getLevel()); + logService.onDidChangeLogLevel(registerTelemetryChannel); + + const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); + const devCategory = nls.localize('developer', "Developer"); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); + } + + private async registerLogChannel(id: string, label: string, file: URI): Promise { + const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); + const exists = await this.fileService.exists(file); + if (exists) { + outputChannelRegistry.registerChannel({ id, label, file, log: true }); + return; + } + + const watcher = this.fileService.watch(dirname(file)); + const disposable = this.fileService.onFileChanges(e => { + if (e.contains(file, FileChangeType.ADDED)) { + watcher.dispose(); + disposable.dispose(); + outputChannelRegistry.registerChannel({ id, label, file, log: true }); + } + }); + } + +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restored); \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/markersModel.ts b/src/vs/workbench/contrib/markers/browser/markersModel.ts index 923f13da0a3..857e74d0cad 100644 --- a/src/vs/workbench/contrib/markers/browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersModel.ts @@ -50,14 +50,7 @@ export class ResourceMarkers { @memoize get name(): string { return basename(this.resource); } - @memoize - get hash(): string { - const hasher = new Hasher(); - hasher.hash(this.resource.toString()); - return `${hasher.value}`; - } - - constructor(readonly resource: URI, readonly markers: Marker[]) { } + constructor(readonly id: string, readonly resource: URI, readonly markers: Marker[]) { } } export class Marker { @@ -73,12 +66,8 @@ export class Marker { return this._lines; } - @memoize - get hash(): string { - return IMarkerData.makeKey(this.marker); - } - constructor( + readonly id: string, readonly marker: IMarker, readonly relatedInformation: RelatedInformation[] = [] ) { } @@ -94,24 +83,8 @@ export class Marker { export class RelatedInformation { - @memoize - get hash(): string { - const hasher = new Hasher(); - hasher.hash(this.resource.toString()); - hasher.hash(this.marker.startLineNumber); - hasher.hash(this.marker.startColumn); - hasher.hash(this.marker.endLineNumber); - hasher.hash(this.marker.endColumn); - hasher.hash(this.raw.resource.toString()); - hasher.hash(this.raw.startLineNumber); - hasher.hash(this.raw.startColumn); - hasher.hash(this.raw.endLineNumber); - hasher.hash(this.raw.endColumn); - return `${hasher.value}`; - } - constructor( - private resource: URI, + readonly id: string, readonly marker: IMarker, readonly raw: IRelatedInformation ) { } @@ -146,23 +119,39 @@ export class MarkersModel { if (isFalsyOrEmpty(rawMarkers)) { this.resourcesByUri.delete(resource.toString()); } else { - const markers = mergeSort(rawMarkers.map(rawMarker => { - let relatedInformation: RelatedInformation[] | undefined = undefined; + const resourceMarkersId = this.id(resource.toString()); + const markersCountByKey = new Map(); + const markers = mergeSort(rawMarkers.map((rawMarker) => { + const key = IMarkerData.makeKey(rawMarker); + const index = markersCountByKey.get(key) || 0; + markersCountByKey.set(key, index + 1); + + const markerId = this.id(resourceMarkersId, key, index); + + let relatedInformation: RelatedInformation[] | undefined = undefined; if (rawMarker.relatedInformation) { - relatedInformation = rawMarker.relatedInformation.map(r => new RelatedInformation(resource, rawMarker, r)); + relatedInformation = rawMarker.relatedInformation.map((r, index) => new RelatedInformation(this.id(markerId, r.resource.toString(), r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn, index), rawMarker, r)); } - return new Marker(rawMarker, relatedInformation); + return new Marker(markerId, rawMarker, relatedInformation); }), compareMarkers); - this.resourcesByUri.set(resource.toString(), new ResourceMarkers(resource, markers)); + this.resourcesByUri.set(resource.toString(), new ResourceMarkers(resourceMarkersId, resource, markers)); } this.cachedSortedResources = undefined; this._onDidChange.fire(resource); } + private id(...values: (string | number)[]): string { + const hasher = new Hasher(); + for (const value of values) { + hasher.hash(value); + } + return `${hasher.value}`; + } + dispose(): void { this._onDidChange.dispose(); this.resourcesByUri.clear(); diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index fde55f553cf..ad11ca74de9 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -305,7 +305,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { const identityProvider = { getId(element: TreeElement) { - return element.hash; + return element.id; } }; diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index c7dafaacfd3..dac61a035c0 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -630,7 +630,7 @@ export class MarkersViewModel extends Disposable { } add(marker: Marker): void { - if (!this.markersViewStates.has(marker.hash)) { + if (!this.markersViewStates.has(marker.id)) { const viewModel = this.instantiationService.createInstance(MarkerViewModel, marker); const disposables: IDisposable[] = [viewModel]; viewModel.multiline = this.multiline; @@ -639,7 +639,7 @@ export class MarkersViewModel extends Disposable { this._onDidChange.fire(marker); } }, this, disposables); - this.markersViewStates.set(marker.hash, { viewModel, disposables }); + this.markersViewStates.set(marker.id, { viewModel, disposables }); const markers = this.markersPerResource.get(marker.resource.toString()) || []; markers.push(marker); @@ -650,11 +650,11 @@ export class MarkersViewModel extends Disposable { remove(resource: URI): void { const markers = this.markersPerResource.get(resource.toString()) || []; for (const marker of markers) { - const value = this.markersViewStates.get(marker.hash); + const value = this.markersViewStates.get(marker.id); if (value) { dispose(value.disposables); } - this.markersViewStates.delete(marker.hash); + this.markersViewStates.delete(marker.id); if (this.hoveredMarker === marker) { this.hoveredMarker = null; } @@ -663,7 +663,7 @@ export class MarkersViewModel extends Disposable { } getViewModel(marker: Marker): MarkerViewModel | null { - const value = this.markersViewStates.get(marker.hash); + const value = this.markersViewStates.get(marker.id); return value ? value.viewModel : null; } diff --git a/src/vs/workbench/contrib/markers/test/electron-browser/markersModel.test.ts b/src/vs/workbench/contrib/markers/test/electron-browser/markersModel.test.ts index 34741e9027c..322c336945c 100644 --- a/src/vs/workbench/contrib/markers/test/electron-browser/markersModel.test.ts +++ b/src/vs/workbench/contrib/markers/test/electron-browser/markersModel.test.ts @@ -27,6 +27,23 @@ class TestMarkersModel extends MarkersModel { suite('MarkersModel Test', () => { + test('marker ids are unique', function () { + const marker1 = anErrorWithRange(3); + const marker2 = anErrorWithRange(3); + const marker3 = aWarningWithRange(3); + const marker4 = aWarningWithRange(3); + + const testObject = new TestMarkersModel([marker1, marker2, marker3, marker4]); + const actuals = testObject.resourceMarkers[0].markers; + + assert.notEqual(actuals[0].id, actuals[1].id); + assert.notEqual(actuals[0].id, actuals[2].id); + assert.notEqual(actuals[0].id, actuals[3].id); + assert.notEqual(actuals[1].id, actuals[2].id); + assert.notEqual(actuals[1].id, actuals[3].id); + assert.notEqual(actuals[2].id, actuals[3].id); + }); + test('sort palces resources with no errors at the end', function () { const marker1 = aMarker('a/res1', MarkerSeverity.Warning); const marker2 = aMarker('a/res2'); @@ -105,22 +122,22 @@ suite('MarkersModel Test', () => { test('toString()', () => { let marker = aMarker('a/res1'); marker.code = '1234'; - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker(marker).toString()); + assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('1', marker).toString()); marker = aMarker('a/res2', MarkerSeverity.Warning); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker(marker).toString()); + assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('2', marker).toString()); marker = aMarker('a/res2', MarkerSeverity.Info, 1, 2, 1, 8, 'Info', ''); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker(marker).toString()); + assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('3', marker).toString()); marker = aMarker('a/res2', MarkerSeverity.Hint, 1, 2, 1, 8, 'Ignore message', 'Ignore'); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker(marker).toString()); + assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), new Marker('4', marker).toString()); marker = aMarker('a/res2', MarkerSeverity.Warning, 1, 2, 1, 8, 'Warning message', '', [{ startLineNumber: 2, startColumn: 5, endLineNumber: 2, endColumn: 10, message: 'some info', resource: URI.file('a/res3') }]); - const testObject = new Marker(marker, null!); + const testObject = new Marker('5', marker, null!); // hack - (testObject as any).relatedInformation = marker.relatedInformation!.map(r => new RelatedInformation(marker.resource, marker, r)); + (testObject as any).relatedInformation = marker.relatedInformation!.map(r => new RelatedInformation('6', marker, r)); assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path, relatedInformation: marker.relatedInformation!.map(r => ({ ...r, resource: r.resource.path })) }, null, '\t'), testObject.toString()); }); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index 7b5eaee508e..0dcc8ef9184 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -327,7 +327,7 @@ export class OutlinePanel extends ViewletPanel { filterOnType: this._outlineViewState.filterOnType, sorter: this._treeComparator, identityProvider: new OutlineIdentityProvider(), - keyboardNavigationLabelProvider: this._instantiationService.createInstance(OutlineNavigationLabelProvider) + keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider() } ) as WorkbenchDataTree; diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts new file mode 100644 index 00000000000..35a40b06fea --- /dev/null +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -0,0 +1,181 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IKeymapService, areKeyboardLayoutsEqual, parseKeyboardLayoutDescription, getKeyboardLayoutId, IKeyboardLayoutInfo } from 'vs/workbench/services/keybinding/common/keymapInfo'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { KEYBOARD_LAYOUT_OPEN_PICKER } from 'vs/workbench/contrib/preferences/common/preferences'; +import { Action } from 'vs/base/common/actions'; +import { isWeb, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { QuickPickInput, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { IEditor } from 'vs/workbench/common/editor'; + +export class KeyboardLayoutPickerContribution extends Disposable implements IWorkbenchContribution { + private readonly pickerElement = this._register(new MutableDisposable()); + + constructor( + @IKeymapService private readonly keymapService: IKeymapService, + @IStatusbarService private readonly statusbarService: IStatusbarService, + ) { + super(); + + let layout = this.keymapService.getCurrentKeyboardLayout(); + if (layout) { + let layoutInfo = parseKeyboardLayoutDescription(layout); + this.pickerElement.value = this.statusbarService.addEntry( + { + text: nls.localize('keyboardLayout', "Layout: {0}", layoutInfo.label), + command: KEYBOARD_LAYOUT_OPEN_PICKER + }, + 'status.workbench.keyboardLayout', + nls.localize('status.workbench.keyboardLayout', "Keyboard Layout"), + StatusbarAlignment.RIGHT + ); + } + + this._register(keymapService.onDidChangeKeyboardMapper(() => { + let layout = this.keymapService.getCurrentKeyboardLayout(); + let layoutInfo = parseKeyboardLayoutDescription(layout); + + if (this.pickerElement.value) { + this.pickerElement.value.update({ + text: nls.localize('keyboardLayout', "Layout: {0}", layoutInfo.label), + command: KEYBOARD_LAYOUT_OPEN_PICKER + }); + } else { + this.pickerElement.value = this.statusbarService.addEntry( + { + text: nls.localize('keyboardLayout', "Layout: {0}", layoutInfo.label), + command: KEYBOARD_LAYOUT_OPEN_PICKER + }, + 'status.workbench.keyboardLayout', + nls.localize('status.workbench.keyboardLayout', "Keyboard Layout"), + StatusbarAlignment.RIGHT + ); + } + })); + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(KeyboardLayoutPickerContribution, LifecyclePhase.Starting); + +interface LayoutQuickPickItem extends IQuickPickItem { + layout: IKeyboardLayoutInfo; +} + +export class KeyboardLayoutPickerAction extends Action { + static readonly ID = KEYBOARD_LAYOUT_OPEN_PICKER; + static readonly LABEL = nls.localize('keyboard.chooseLayout', "Change Keyboard Layout"); + + private static DEFAULT_CONTENT: string = [ + `// ${nls.localize('displayLanguage', 'Defines the keyboard layout used in VS Code in the browser environment.')}`, + `// ${nls.localize('doc', 'Open VS Code and run "Developer: Inspect Key Mappings (JSON)" from Command Palette.')}`, + ``, + `// Once you have the keyboard layout info, please paste it below.`, + '\n' + ].join('\n'); + + constructor( + actionId: string, + actionLabel: string, + @IFileService private readonly fileService: IFileService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IKeymapService private readonly keymapService: IKeymapService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEditorService private readonly editorService: IEditorService + ) { + super(actionId, actionLabel); + + this.enabled = isWeb; + } + + async run(): Promise { + let layouts = this.keymapService.getAllKeyboardLayouts(); + let currentLayout = this.keymapService.getCurrentKeyboardLayout(); + let layoutConfig = this.configurationService.getValue('keyboard.layout'); + let isAutoDetect = layoutConfig === 'autodetect'; + + const picks: QuickPickInput[] = layouts.map(layout => { + const picked = !isAutoDetect && areKeyboardLayoutsEqual(currentLayout, layout); + const layoutInfo = parseKeyboardLayoutDescription(layout); + return { + layout: layout, + label: [layoutInfo.label, (layout && layout.isUserKeyboardLayout) ? '(User configured layout)' : ''].join(' '), + id: (layout).text || (layout).lang || (layout).layout, + description: layoutInfo.description + (picked ? ' (Current layout)' : ''), + picked: !isAutoDetect && areKeyboardLayoutsEqual(currentLayout, layout) + }; + }).sort((a: IQuickPickItem, b: IQuickPickItem) => { + return a.label < b.label ? -1 : (a.label > b.label ? 1 : 0); + }); + + if (picks.length > 0) { + const platform = isMacintosh ? 'Mac' : isWindows ? 'Win' : 'Linux'; + picks.unshift({ type: 'separator', label: nls.localize('layoutPicks', "Keyboard Layouts ({0})", platform) }); + } + + let configureKeyboardLayout: IQuickPickItem = { label: nls.localize('configureKeyboardLayout', "Configure Keyboard Layout") }; + + picks.unshift(configureKeyboardLayout); + + // Offer to "Auto Detect" + const autoDetectMode: IQuickPickItem = { + label: nls.localize('autoDetect', "Auto Detect"), + description: isAutoDetect ? `Current: ${parseKeyboardLayoutDescription(currentLayout).label}` : undefined, + picked: isAutoDetect ? true : undefined + }; + + picks.unshift(autoDetectMode); + + const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickKeyboardLayout', "Select Keyboard Layout"), matchOnDescription: true }); + if (!pick) { + return; + } + + if (pick === autoDetectMode) { + // set keymap service to auto mode + this.configurationService.updateValue('keyboard.layout', 'autodetect'); + return; + } + + if (pick === configureKeyboardLayout) { + const file = this.environmentService.keyboardLayoutResource; + + await this.fileService.resolve(file).then(undefined, (error) => { + return this.fileService.createFile(file, VSBuffer.fromString(KeyboardLayoutPickerAction.DEFAULT_CONTENT)); + }).then((stat): Promise | null => { + if (!stat) { + return null; + } + return this.editorService.openEditor({ + resource: stat.resource, + mode: 'jsonc' + }); + }, (error) => { + throw new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", file.toString(), error)); + }); + + return Promise.resolve(); + } + + this.configurationService.updateValue('keyboard.layout', getKeyboardLayoutId((pick).layout)); + } +} + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(new SyncActionDescriptor(KeyboardLayoutPickerAction, KeyboardLayoutPickerAction.ID, KeyboardLayoutPickerAction.LABEL, {}), 'Preferences: Change Keyboard Layout', nls.localize('preferences', "Preferences")); diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 4b11273f830..43d1569f305 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -237,8 +237,6 @@ } .settings-editor > .settings-body .settings-tree-container { - margin-right: 1px; - /* So the item doesn't blend into the edge of the view container */ margin-top: 14px; border-spacing: 0; border-collapse: separate; diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 00670dcbdb1..89039aff9a2 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -742,7 +742,7 @@ CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, serviceA if (control instanceof SettingsEditor2) { control.focusSearch(`@tag:usesOnlineServices`); } else { - serviceAccessor.get(IPreferencesService).openSettings(undefined, '@tag:usesOnlineServices'); + serviceAccessor.get(IPreferencesService).openSettings(false, '@tag:usesOnlineServices'); } }); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 2600517f9b1..6912a5f72a6 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -31,7 +31,7 @@ import { ConfigurationTarget } from 'vs/platform/configuration/common/configurat import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -94,7 +94,7 @@ export class PreferencesEditor extends BaseEditor { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @ILocalProgressService private readonly progressService: ILocalProgressService, + @IEditorProgressService private readonly editorProgressService: IEditorProgressService, @IStorageService storageService: IStorageService ) { super(PreferencesEditor.ID, telemetryService, themeService, storageService); @@ -237,7 +237,7 @@ export class PreferencesEditor extends BaseEditor { if (query) { return Promise.all([ this.localSearchDelayer.trigger(() => this.preferencesRenderers.localFilterPreferences(query).then(() => { })), - this.remoteSearchThrottle.trigger(() => Promise.resolve(this.progressService.showWhile(this.preferencesRenderers.remoteSearchPreferences(query), 500))) + this.remoteSearchThrottle.trigger(() => Promise.resolve(this.editorProgressService.showWhile(this.preferencesRenderers.remoteSearchPreferences(query), 500))) ]).then(() => { }); } else { // When clearing the input, update immediately to clear it diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index 16642bb1943..b0b5540271f 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -111,3 +111,5 @@ export const MODIFIED_SETTING_TAG = 'modified'; export const EXTENSION_SETTING_TAG = 'ext:'; export const SETTINGS_COMMAND_OPEN_SETTINGS = 'workbench.action.openSettings'; + +export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker'; diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts new file mode 100644 index 00000000000..9235c739fb0 --- /dev/null +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { isWeb, OperatingSystem } from 'vs/base/common/platform'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Schemas } from 'vs/base/common/network'; +import { IRemoteAgentService, RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { LogLevelSetterChannelClient } from 'vs/platform/log/common/logIpc'; +import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; +import { localize } from 'vs/nls'; +import { joinPath } from 'vs/base/common/resources'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export class LabelContribution implements IWorkbenchContribution { + constructor( + @ILabelService private readonly labelService: ILabelService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService) { + this.registerFormatters(); + } + + private registerFormatters(): void { + if (isWeb) { + this.remoteAgentService.getEnvironment().then(remoteEnvironment => { + if (remoteEnvironment) { + this.labelService.registerFormatter({ + scheme: Schemas.vscodeRemote, + authority: this.environmentService.configuration.remoteAuthority, + formatting: { + label: '${path}', + separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', + tildify: remoteEnvironment.os !== OperatingSystem.Windows + } + }); + } + }); + } + } +} + +class RemoteChannelsContribution extends Disposable implements IWorkbenchContribution { + + constructor( + @ILogService logService: ILogService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + ) { + super(); + const connection = remoteAgentService.getConnection(); + if (connection) { + const logLevelClient = new LogLevelSetterChannelClient(connection.getChannel('loglevel')); + logLevelClient.setLevel(logService.getLevel()); + this._register(logService.onDidChangeLogLevel(level => logLevelClient.setLevel(level))); + } + } +} + +class RemoteLogOutputChannels implements IWorkbenchContribution { + + constructor( + @IRemoteAgentService remoteAgentService: IRemoteAgentService + ) { + remoteAgentService.getEnvironment().then(remoteEnv => { + if (remoteEnv) { + const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); + outputChannelRegistry.registerChannel({ id: 'remoteExtensionLog', label: localize('remoteExtensionLog', "Remote Server"), file: joinPath(remoteEnv.logsPath, `${RemoteExtensionLogFileName}.log`), log: true }); + } + }); + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(LabelContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution(RemoteLogOutputChannels, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 4998737042e..78264965a27 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -8,13 +8,11 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { RemoteExtensionLogFileName, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; -import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/contrib/output/common/output'; -import * as resources from 'vs/base/common/resources'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -24,11 +22,10 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ILogService } from 'vs/platform/log/common/log'; -import { IFileService } from 'vs/platform/files/common/files'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc'; -import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; +import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer as ipc } from 'electron'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -37,7 +34,7 @@ import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAg import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import Severity from 'vs/base/common/severity'; -import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions/windowActions'; +import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { RemoteConnectionState } from 'vs/workbench/browser/contextkeys'; @@ -213,27 +210,11 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc } } -class LogOutputChannels extends Disposable implements IWorkbenchContribution { - - constructor( - @IRemoteAgentService remoteAgentService: IRemoteAgentService - ) { - super(); - remoteAgentService.getEnvironment().then(remoteEnv => { - if (remoteEnv) { - const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - outputChannelRegistry.registerChannel({ id: 'remoteExtensionLog', label: nls.localize('remoteExtensionLog', "Remote Server"), file: resources.joinPath(remoteEnv.logsPath, `${RemoteExtensionLogFileName}.log`), log: true }); - } - }); - } -} - class RemoteChannelsContribution implements IWorkbenchContribution { constructor( @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IFileService fileService: IFileService, @IDialogService dialogService: IDialogService ) { const connection = remoteAgentService.getConnection(); @@ -418,7 +399,6 @@ class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchC const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting); -workbenchContributionsRegistry.registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index 08017c9db47..d732bf2fae3 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -73,7 +73,6 @@ flex: 1; font-size: 11px; font-weight: bold; - text-transform: uppercase; overflow: hidden; text-overflow: ellipsis; } diff --git a/src/vs/workbench/contrib/scm/browser/scmActivity.ts b/src/vs/workbench/contrib/scm/browser/scmActivity.ts index 2bcd79a66f8..de8d68429cf 100644 --- a/src/vs/workbench/contrib/scm/browser/scmActivity.ts +++ b/src/vs/workbench/contrib/scm/browser/scmActivity.ts @@ -189,20 +189,12 @@ export class StatusBarController implements IWorkbenchContribution { const disposables = new DisposableStore(); for (const c of commands) { - const statusId = `status.scm.${repository.provider.id}.${c.tooltip}`; // needs to be unique, but c.id is too random - let statusLabel: string; - if (c.tooltip) { - statusLabel = localize('status.scm', "Source Control ({0}): {1}", repository.provider.label, c.tooltip.replace('...', '')); - } else { - statusLabel = localize('status.scm.short', "Source Control ({0})", repository.provider.label); - } - disposables.add(this.statusbarService.addEntry({ text: c.title, tooltip: `${label} - ${c.tooltip}`, command: c.id, arguments: c.arguments - }, statusId, statusLabel, MainThreadStatusBarAlignment.LEFT, 10000)); + }, 'status.scm', localize('status.scm', "Source Control"), MainThreadStatusBarAlignment.LEFT, 10000)); } this.statusBarDisposable = disposables; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 5056192bf77..32e0cbbe862 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { combinedDisposable, dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { combinedDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import * as resources from 'vs/base/common/resources'; import { endsWith, isFalsyOrWhitespace } from 'vs/base/common/strings'; @@ -129,7 +129,7 @@ class SnippetsService implements ISnippetsService { readonly _serviceBrand: any; - private readonly _disposables: IDisposable[] = []; + private readonly _disposables = new DisposableStore(); private readonly _pendingWork: Promise[] = []; private readonly _files = new Map(); @@ -151,7 +151,7 @@ class SnippetsService implements ISnippetsService { } dispose(): void { - dispose(this._disposables); + this._disposables.dispose(); } private _joinSnippets(): Promise { @@ -256,20 +256,18 @@ class SnippetsService implements ISnippetsService { private _initWorkspaceSnippets(): void { // workspace stuff - let disposables: IDisposable[] = []; + let disposables = new DisposableStore(); let updateWorkspaceSnippets = () => { - disposables = dispose(disposables); + disposables.clear(); this._pendingWork.push(this._initWorkspaceFolderSnippets(this._contextService.getWorkspace(), disposables)); }; - this._disposables.push({ - dispose() { dispose(disposables); } - }); - this._disposables.push(this._contextService.onDidChangeWorkspaceFolders(updateWorkspaceSnippets)); - this._disposables.push(this._contextService.onDidChangeWorkbenchState(updateWorkspaceSnippets)); + this._disposables.add(disposables); + this._disposables.add(this._contextService.onDidChangeWorkspaceFolders(updateWorkspaceSnippets)); + this._disposables.add(this._contextService.onDidChangeWorkbenchState(updateWorkspaceSnippets)); updateWorkspaceSnippets(); } - private _initWorkspaceFolderSnippets(workspace: IWorkspace, bucket: IDisposable[]): Promise { + private _initWorkspaceFolderSnippets(workspace: IWorkspace, bucket: DisposableStore): Promise { let promises = workspace.folders.map(folder => { const snippetFolder = folder.toResource('.vscode'); return this._fileService.exists(snippetFolder).then(value => { @@ -277,8 +275,8 @@ class SnippetsService implements ISnippetsService { this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket); } else { // watch - bucket.push(watch(this._fileService, snippetFolder, (type) => { - if (type === FileChangeType.ADDED) { + bucket.add(this._fileService.onFileChanges(e => { + if (e.contains(snippetFolder, FileChangeType.ADDED)) { this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket); } })); @@ -293,7 +291,7 @@ class SnippetsService implements ISnippetsService { return this._fileService.createFolder(userSnippetsFolder).then(() => this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables)); } - private _initFolderSnippets(source: SnippetSource, folder: URI, bucket: IDisposable[]): Promise { + private _initFolderSnippets(source: SnippetSource, folder: URI, bucket: DisposableStore): Promise { const disposables = new DisposableStore(); const addFolderSnippets = (type?: FileChangeType) => { disposables.clear(); @@ -310,8 +308,8 @@ class SnippetsService implements ISnippetsService { }); }; - bucket.push(watch(this._fileService, folder, addFolderSnippets)); - bucket.push(disposables); + bucket.add(watch(this._fileService, folder, addFolderSnippets)); + bucket.add(disposables); return addFolderSnippets(); } diff --git a/src/vs/workbench/contrib/stats/node/stats.contribution.ts b/src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts similarity index 89% rename from src/vs/workbench/contrib/stats/node/stats.contribution.ts rename to src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts index ee3b9b175d5..b2db16dac1c 100644 --- a/src/vs/workbench/contrib/stats/node/stats.contribution.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/stats.contribution.ts @@ -5,7 +5,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { WorkspaceStats } from 'vs/workbench/contrib/stats/node/workspaceStats'; +import { WorkspaceStats } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // Register Workspace Stats Contribution diff --git a/src/vs/workbench/contrib/stats/node/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts similarity index 98% rename from src/vs/workbench/contrib/stats/node/workspaceStats.ts rename to src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index db4fcadd84a..8504522b3ef 100644 --- a/src/vs/workbench/contrib/stats/node/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -21,6 +21,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { joinPath } from 'vs/base/common/resources'; import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -224,7 +225,8 @@ export class WorkspaceStats implements IWorkbenchContribution { @INotificationService private readonly notificationService: INotificationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IStorageService private readonly storageService: IStorageService, - @ITextFileService private readonly textFileService: ITextFileService + @ITextFileService private readonly textFileService: ITextFileService, + @ISharedProcessService private readonly sharedProcessService: ISharedProcessService ) { this.report(); } @@ -239,6 +241,9 @@ export class WorkspaceStats implements IWorkbenchContribution { this.reportCloudStats(); this.reportProxyStats(); + + const diagnosticsChannel = this.sharedProcessService.getChannel('diagnostics'); + diagnosticsChannel.call('reportWorkspaceStats', this.contextService.getWorkspace()); } private static searchArray(arr: string[], regEx: RegExp): boolean | undefined { diff --git a/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts b/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts index 3543931c185..3cb2162ecdb 100644 --- a/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts +++ b/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as crypto from 'crypto'; -import { getDomainsOfRemotes, getRemotes, getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/node/workspaceStats'; +import { getDomainsOfRemotes, getRemotes, getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; function hash(value: string): string { return crypto.createHash('sha1').update(value.toString()).digest('hex'); diff --git a/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts index 447a3565881..ec9ac3fb2b7 100644 --- a/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts @@ -11,7 +11,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import pkg from 'vs/platform/product/node/package'; -import product, { ISurveyData } from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/node/product'; +import { ISurveyData } from 'vs/platform/product/common/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 9db06160e9e..ed18efcb64a 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -72,6 +72,7 @@ import { RunAutomaticTasks } from 'vs/workbench/contrib/tasks/browser/runAutomat import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; @@ -220,6 +221,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ITerminalInstanceService private readonly terminalInstanceService: ITerminalInstanceService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService ) { super(); @@ -1094,6 +1096,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.modelService, this.configurationResolverService, this.telemetryService, this.contextService, this.environmentService, AbstractTaskService.OutputChannelId, this.fileService, this.terminalInstanceService, + this.remoteAgentService, (workspaceFolder: IWorkspaceFolder) => { if (!workspaceFolder) { return undefined; diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index fc0fea48fa4..c6e1d3fbaa7 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -43,6 +43,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { Schemas } from 'vs/base/common/network'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; interface TerminalData { terminal: ITerminalInstance; @@ -144,9 +145,9 @@ export class TerminalTaskSystem implements ITaskSystem { }; private static osShellQuotes: IStringDictionary = { - 'linux': TerminalTaskSystem.shellQuotes['bash'], - 'darwin': TerminalTaskSystem.shellQuotes['bash'], - 'win32': TerminalTaskSystem.shellQuotes['powershell'] + 'Linux': TerminalTaskSystem.shellQuotes['bash'], + 'Mac': TerminalTaskSystem.shellQuotes['bash'], + 'Windows': TerminalTaskSystem.shellQuotes['powershell'] }; private activeTasks: IStringDictionary; @@ -172,6 +173,7 @@ export class TerminalTaskSystem implements ITaskSystem { private outputChannelId: string, private fileService: IFileService, private terminalInstanceService: ITerminalInstanceService, + private remoteAgentService: IRemoteAgentService, taskSystemInfoResolver: TaskSystemInfoResolver, ) { @@ -506,69 +508,74 @@ export class TerminalTaskSystem implements ITaskSystem { } } - private executeInTerminal(task: CustomTask | ContributedTask, trigger: string, resolver: VariableResolver): Promise { + private async executeInTerminal(task: CustomTask | ContributedTask, trigger: string, resolver: VariableResolver): Promise { let terminal: ITerminalInstance | undefined = undefined; let executedCommand: string | undefined = undefined; let error: TaskError | undefined = undefined; let promise: Promise | undefined = undefined; if (task.configurationProperties.isBackground) { - promise = new Promise((resolve, reject) => { - const problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); - let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService, this.fileService); - let toDispose: IDisposable[] | undefined = []; - let eventCounter: number = 0; - toDispose.push(watchingProblemMatcher.onDidStateChange((event) => { - if (event.kind === ProblemCollectorEventKind.BackgroundProcessingBegins) { - eventCounter++; - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); - } else if (event.kind === ProblemCollectorEventKind.BackgroundProcessingEnds) { - eventCounter--; - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); - if (eventCounter === 0) { - if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity && - (watchingProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error)) { - let reveal = task.command.presentation!.reveal; - let revealProblems = task.command.presentation!.revealProblems; - if (revealProblems === RevealProblemKind.OnProblem) { - this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true); - } else if (reveal === RevealKind.Silent) { - this.terminalService.setActiveInstance(terminal!); - this.terminalService.showPanel(false); - } + const problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); + let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService, this.fileService); + let toDispose: IDisposable[] | undefined = []; + let eventCounter: number = 0; + toDispose.push(watchingProblemMatcher.onDidStateChange((event) => { + if (event.kind === ProblemCollectorEventKind.BackgroundProcessingBegins) { + eventCounter++; + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); + } else if (event.kind === ProblemCollectorEventKind.BackgroundProcessingEnds) { + eventCounter--; + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); + if (eventCounter === 0) { + if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity && + (watchingProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error)) { + let reveal = task.command.presentation!.reveal; + let revealProblems = task.command.presentation!.revealProblems; + if (revealProblems === RevealProblemKind.OnProblem) { + this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true); + } else if (reveal === RevealKind.Silent) { + this.terminalService.setActiveInstance(terminal!); + this.terminalService.showPanel(false); } } } - })); - watchingProblemMatcher.aboutToStart(); - let delayer: Async.Delayer | undefined = undefined; - [terminal, executedCommand, error] = this.createTerminal(task, resolver); - if (error || !terminal) { - return; } - let processStartedSignaled = false; - terminal.processReady.then(() => { - if (!processStartedSignaled) { - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); - } - processStartedSignaled = true; + })); + watchingProblemMatcher.aboutToStart(); + let delayer: Async.Delayer | undefined = undefined; + [terminal, executedCommand, error] = await this.createTerminal(task, resolver); + + if (error) { + return Promise.reject(new Error((error).message)); + } + if (!terminal) { + return Promise.reject(new Error(`Failed to create terminal for task ${task._label}`)); + } + + let processStartedSignaled = false; + terminal.processReady.then(() => { + if (!processStartedSignaled) { + if (task.command.runtime !== RuntimeType.CustomExecution) { + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); } - }, (_error) => { - // The process never got ready. Need to think how to handle this. + processStartedSignaled = true; + } + }, (_error) => { + // The process never got ready. Need to think how to handle this. + }); + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); + const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); + const onData = terminal.onLineData((line) => { + watchingProblemMatcher.processLine(line); + if (!delayer) { + delayer = new Async.Delayer(3000); + } + delayer.trigger(() => { + watchingProblemMatcher.forceDelivery(); + delayer = undefined; }); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); - const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); - const onData = terminal.onLineData((line) => { - watchingProblemMatcher.processLine(line); - if (!delayer) { - delayer = new Async.Delayer(3000); - } - delayer.trigger(() => { - watchingProblemMatcher.forceDelivery(); - delayer = undefined; - }); - }); - const onExit = terminal.onExit((exitCode) => { + }); + promise = new Promise((resolve, reject) => { + const onExit = terminal!.onExit((exitCode) => { onData.dispose(); onExit.dispose(); let key = task.getMapKey(); @@ -615,32 +622,36 @@ export class TerminalTaskSystem implements ITaskSystem { }); }); } else { - promise = new Promise((resolve, reject) => { - [terminal, executedCommand, error] = this.createTerminal(task, resolver); - if (!terminal || error) { - return; - } + [terminal, executedCommand, error] = await this.createTerminal(task, resolver); - let processStartedSignaled = false; - terminal.processReady.then(() => { - if (!processStartedSignaled) { - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); - } - processStartedSignaled = true; + if (error) { + return Promise.reject(new Error((error).message)); + } + if (!terminal) { + return Promise.reject(new Error(`Failed to create terminal for task ${task._label}`)); + } + + let processStartedSignaled = false; + terminal.processReady.then(() => { + if (!processStartedSignaled) { + if (task.command.runtime !== RuntimeType.CustomExecution) { + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); } - }, (_error) => { - // The process never got ready. Need to think how to handle this. - }); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); - let problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); - let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService, ProblemHandlingStrategy.Clean, this.fileService); - const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); - const onData = terminal.onLineData((line) => { - startStopProblemMatcher.processLine(line); - }); - const onExit = terminal.onExit((exitCode) => { + processStartedSignaled = true; + } + }, (_error) => { + // The process never got ready. Need to think how to handle this. + }); + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); + let problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); + let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService, ProblemHandlingStrategy.Clean, this.fileService); + const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); + const onData = terminal.onLineData((line) => { + startStopProblemMatcher.processLine(line); + }); + promise = new Promise((resolve, reject) => { + const onExit = terminal!.onExit((exitCode) => { onData.dispose(); onExit.dispose(); let key = task.getMapKey(); @@ -687,12 +698,7 @@ export class TerminalTaskSystem implements ITaskSystem { }); }); } - if (error) { - return Promise.reject(new Error((error).message)); - } - if (!terminal) { - return Promise.reject(new Error(`Failed to create terminal for task ${task._label}`)); - } + let showProblemPanel = task.command.presentation && (task.command.presentation.revealProblems === RevealProblemKind.Always); if (showProblemPanel) { this.panelService.openPanel(Constants.MARKERS_PANEL_ID); @@ -751,34 +757,23 @@ export class TerminalTaskSystem implements ITaskSystem { return nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', needsFolderQualification ? task.getQualifiedLabel() : task.configurationProperties.name); } - private getDefaultShell(platform: Platform.Platform): string { - let defaultShell: string | undefined = undefined; - try { - defaultShell = this.terminalInstanceService.getDefaultShell(platform); - } catch { - // Do nothing + private async getUserHome(): Promise { + const env = await this.remoteAgentService.getEnvironment(); + if (env) { + return env.userHome; } - if (!defaultShell) { - // Make up a guess for the default shell. - if (platform === Platform.Platform.Windows) { - defaultShell = 'cmd.exe'; - } else { - defaultShell = 'bash'; - } - console.warn('Cannot get the default shell.'); - } - return defaultShell; + return URI.from({ scheme: Schemas.file, path: this.environmentService.userHome }); } - private createShellLaunchConfig(task: CustomTask | ContributedTask, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: boolean | string): IShellLaunchConfig | undefined { + private async createShellLaunchConfig(task: CustomTask | ContributedTask, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: boolean | string): Promise { let shellLaunchConfig: IShellLaunchConfig; let isShellCommand = task.command.runtime === RuntimeType.Shell; let needsFolderQualification = this.currentTask.workspaceFolder && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; let terminalName = this.createTerminalName(task); let originalCommand = task.command.name; if (isShellCommand) { - shellLaunchConfig = { name: terminalName, executable: undefined, args: undefined, waitOnExit }; - this.terminalInstanceService.mergeDefaultShellPathAndArgs(shellLaunchConfig, this.getDefaultShell(platform), this.terminalService.configHelper, platform); + const defaultConfig = await this.terminalInstanceService.getDefaultShellAndArgs(platform); + shellLaunchConfig = { name: terminalName, executable: defaultConfig.shell, args: defaultConfig.args, waitOnExit }; let shellSpecified: boolean = false; let shellOptions: ShellConfiguration | undefined = task.command.options && task.command.options.shell; if (shellOptions) { @@ -792,23 +787,16 @@ export class TerminalTaskSystem implements ITaskSystem { shellLaunchConfig.args = []; } } - let shellArgs = shellLaunchConfig.args!.slice(0); + let shellArgs = Array.isArray(shellLaunchConfig.args!) ? shellLaunchConfig.args!.slice(0) : [shellLaunchConfig.args!]; let toAdd: string[] = []; let commandLine = this.buildShellCommandLine(platform, shellLaunchConfig.executable!, shellOptions, command, originalCommand, args); let windowsShellArgs: boolean = false; if (platform === Platform.Platform.Windows) { - // Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's - // safe to assume that this was used by accident as Sysnative does not - // exist and will break the terminal in non-WoW64 environments. - if (!process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) { - const sysnativePath = path.join(process.env.windir!, 'Sysnative').toLowerCase(); - if (shellLaunchConfig.executable!.toLowerCase().indexOf(sysnativePath) === 0) { - shellLaunchConfig.executable = path.join(process.env.windir!, 'System32', shellLaunchConfig.executable!.substr(sysnativePath.length)); - } - } windowsShellArgs = true; let basename = path.basename(shellLaunchConfig.executable!).toLowerCase(); - if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(process.cwd())))) { + // If we don't have a cwd, then the terminal uses the home dir. + const userHome = await this.getUserHome(); + if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath)))) { return undefined; } if ((basename === 'powershell.exe') || (basename === 'pwsh.exe')) { @@ -903,7 +891,7 @@ export class TerminalTaskSystem implements ITaskSystem { return shellLaunchConfig; } - private createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver): [ITerminalInstance | undefined, string | undefined, TaskError | undefined] { + private async createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver): Promise<[ITerminalInstance | undefined, string | undefined, TaskError | undefined]> { let platform = resolver.taskSystemInfo ? resolver.taskSystemInfo.platform : Platform.platform; let options = this.resolveOptions(resolver, task.command.options); @@ -940,7 +928,7 @@ export class TerminalTaskSystem implements ITaskSystem { args = resolvedResult.args; commandExecutable = CommandString.value(command); - this.currentTask.shellLaunchConfig = this.isRerun ? this.lastTask.getVerifiedTask().shellLaunchConfig : this.createShellLaunchConfig(task, resolver, platform, options, command, args, waitOnExit); + this.currentTask.shellLaunchConfig = this.isRerun ? this.lastTask.getVerifiedTask().shellLaunchConfig : await this.createShellLaunchConfig(task, resolver, platform, options, command, args, waitOnExit); if (this.currentTask.shellLaunchConfig === undefined) { return [undefined, undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)]; } @@ -1031,7 +1019,7 @@ export class TerminalTaskSystem implements ITaskSystem { private buildShellCommandLine(platform: Platform.Platform, shellExecutable: string, shellOptions: ShellConfiguration | undefined, command: CommandString, originalCommand: CommandString | undefined, args: CommandString[]): string { let basename = path.parse(shellExecutable).name.toLowerCase(); - let shellQuoteOptions = this.getQuotingOptions(basename, shellOptions); + let shellQuoteOptions = this.getQuotingOptions(basename, shellOptions, platform); function needsQuotes(value: string): boolean { if (value.length >= 2) { @@ -1131,11 +1119,11 @@ export class TerminalTaskSystem implements ITaskSystem { return commandLine; } - private getQuotingOptions(shellBasename: string, shellOptions: ShellConfiguration | undefined): ShellQuotingOptions { + private getQuotingOptions(shellBasename: string, shellOptions: ShellConfiguration | undefined, platform: Platform.Platform): ShellQuotingOptions { if (shellOptions && shellOptions.quoting) { return shellOptions.quoting; } - return TerminalTaskSystem.shellQuotes[shellBasename] || TerminalTaskSystem.osShellQuotes[process.platform]; + return TerminalTaskSystem.shellQuotes[shellBasename] || TerminalTaskSystem.osShellQuotes[Platform.PlatformToString(platform)]; } private collectTaskVariables(variables: Set, task: CustomTask | ContributedTask): void { diff --git a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts index 388846988a3..631275db0d0 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts @@ -3,34 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import * as Objects from 'vs/base/common/objects'; import * as semver from 'semver'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ITaskSystem } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { ExecutionEngine, TaskRunSource } from 'vs/workbench/contrib/tasks/common/tasks'; import * as TaskConfig from '../common/taskConfiguration'; import { ProcessTaskSystem } from 'vs/workbench/contrib/tasks/node/processTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/contrib/tasks/node/processRunnerDetector'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { RunAutomaticTasks, AllowAutomaticTaskRunning, DisallowAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; import { AbstractTaskService } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; import { TaskFilter } from 'vs/workbench/contrib/tasks/common/taskService'; -let tasksCategory = nls.localize('tasksCategory', "Tasks"); - -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); - -const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowAutomaticTaskRunning, AllowAutomaticTaskRunning.ID, AllowAutomaticTaskRunning.LABEL), 'Tasks: Allow Automatic Tasks in Folder', tasksCategory); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowAutomaticTaskRunning, DisallowAutomaticTaskRunning.ID, DisallowAutomaticTaskRunning.LABEL), 'Tasks: Disallow Automatic Tasks in Folder', tasksCategory); - interface WorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; config: TaskConfig.ExternalTaskRunnerConfiguration | undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 37429d9646b..5bfff739f8d 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -43,6 +43,8 @@ font-feature-settings: "liga" 0; position: relative; user-select: none; + -ms-user-select: none; + -webkit-user-select: none; } .xterm.focus, @@ -127,13 +129,22 @@ line-height: normal; } +.xterm { + cursor: text; +} + .xterm.enable-mouse-events { - /* When mouse events are enabled (e.g. tmux), revert to the standard pointer cursor */ + /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ cursor: default; } -.xterm:not(.enable-mouse-events) { - cursor: text; +.xterm.xterm-cursor-pointer { + cursor: pointer; +} + +.xterm.column-select.focus { + /* Column selection mode */ + cursor: crosshair; } .xterm .xterm-accessibility, @@ -147,10 +158,6 @@ color: transparent; } -.xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] { - outline: 1px solid #F80; -} - .xterm .live-region { position: absolute; left: -9999px; @@ -159,11 +166,10 @@ overflow: hidden; } -.xterm-cursor-pointer { - cursor: pointer !important; +.xterm-dim { + opacity: 0.5; } -.xterm.xterm-cursor-crosshair { - /* Column selection mode */ - cursor: crosshair !important; +.xterm-underline { + text-decoration: underline; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 2ab913b9cfc..e2e01bbb8b6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -23,7 +23,7 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor import { AllowWorkspaceShellTerminalCommand, ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, DisallowWorkspaceShellTerminalCommand, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; -import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, ITerminalService, TERMINAL_ACTION_CATEGORY } from 'vs/workbench/contrib/terminal/common/terminal'; import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { setupTerminalCommands, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; import { setupTerminalMenu } from 'vs/workbench/contrib/terminal/common/terminalMenu'; @@ -302,7 +302,7 @@ actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTerm Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERMINAL_PANEL_ID); // On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl -const category = nls.localize('terminalCategory', "Terminal"); +const category = TERMINAL_ACTION_CATEGORY; const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, { @@ -370,9 +370,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAct primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category); -if (platform.isWindows) { - actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); -} +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowWorkspaceShellTerminalCommand, AllowWorkspaceShellTerminalCommand.ID, AllowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Allow Workspace Shell Configuration', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand.ID, DisallowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Disallow Workspace Shell Configuration', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index b7648c67e3c..c9e3bdb74a3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -6,9 +6,10 @@ import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; -import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; +import { Event } from 'vs/base/common/event'; export const ITerminalInstanceService = createDecorator('terminalInstanceService'); @@ -20,16 +21,16 @@ export const ITerminalInstanceService = createDecorator; + getXtermConstructor(): Promise; getXtermWebLinksConstructor(): Promise; getXtermSearchConstructor(): Promise; createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; - getDefaultShell(p: Platform): string; - /** - * Merges the default shell path and args into the provided launch configuration - */ - mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, defaultShell: string, configHelper: ITerminalConfigHelper, platformOverride?: Platform): void; + + getDefaultShellAndArgs(platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>; getMainProcessParentEnv(): Promise; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 19d55b65081..0e01b387a47 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper, ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -35,7 +35,6 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export const TERMINAL_PICKER_PREFIX = 'term '; @@ -624,13 +623,13 @@ export class SelectDefaultShellWindowsTerminalAction extends Action { constructor( id: string, label: string, - @ITerminalNativeService private readonly _terminalNativeService: ITerminalNativeService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(id, label); } public run(event?: any): Promise { - return this._terminalNativeService.selectDefaultWindowsShell(); + return this._terminalService.selectDefaultWindowsShell(); } } @@ -712,8 +711,7 @@ export class SwitchTerminalAction extends Action { constructor( id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService, - @ITerminalNativeService private readonly terminalNativeService: ITerminalNativeService + @ITerminalService private readonly terminalService: ITerminalService ) { super(id, label, 'terminal-action switch-terminal'); } @@ -728,7 +726,7 @@ export class SwitchTerminalAction extends Action { } if (item === SelectDefaultShellWindowsTerminalAction.LABEL) { this.terminalService.refreshActiveTab(); - return this.terminalNativeService.selectDefaultWindowsShell(); + return this.terminalService.selectDefaultWindowsShell(); } const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1; this.terminalService.setActiveTabByIndex(selectedTabIndex); @@ -744,8 +742,7 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { action: IAction, @ITerminalService private readonly terminalService: ITerminalService, @IThemeService themeService: IThemeService, - @IContextViewService contextViewService: IContextViewService, - @IWorkbenchEnvironmentService private workbenchEnvironmentService: IWorkbenchEnvironmentService + @IContextViewService contextViewService: IContextViewService ) { super(null, action, terminalService.getTabLabels().map(label => { text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') }); @@ -757,11 +754,8 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { private _updateItems(): void { const items = this.terminalService.getTabLabels().map(label => { text: label }); - let enableSelectDefaultShell = this.workbenchEnvironmentService.configuration.remoteAuthority ? false : isWindows; - if (enableSelectDefaultShell) { - items.push({ text: SwitchTerminalActionViewItem.SEPARATOR }); - items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL }); - } + items.push({ text: SwitchTerminalActionViewItem.SEPARATOR }); + items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL }); this.setOptions(items, this.terminalService.activeTabIndex); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts index c50fcfc8496..77822137bcd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts @@ -112,7 +112,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa private _scrollToMarker(marker: IMarker, position: ScrollPosition): void { let line = marker.line; if (position === ScrollPosition.Middle) { - line = Math.max(line - this._xterm.rows / 2, 0); + line = Math.max(line - Math.floor(this._xterm.rows / 2), 0); } this._xterm.scrollToLine(line); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9584762836c..0c0f6721e9e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -201,6 +201,8 @@ export class TerminalInstance implements ITerminalInstance { } return this._rows; } + public get maxCols(): number { return this._cols; } + public get maxRows(): number { return this._rows; } // TODO: Ideally processId would be merged into processReady public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; } // TODO: How does this work with detached processes? @@ -232,6 +234,8 @@ export class TerminalInstance implements ITerminalInstance { public get onRequestExtHostProcess(): Event { return this._onRequestExtHostProcess.event; } private readonly _onDimensionsChanged = new Emitter(); public get onDimensionsChanged(): Event { return this._onDimensionsChanged.event; } + private readonly _onMaximumDimensionsChanged = new Emitter(); + public get onMaximumDimensionsChanged(): Event { return this._onMaximumDimensionsChanged.event; } private readonly _onFocus = new Emitter(); public get onFocus(): Event { return this._onFocus.event; } @@ -349,12 +353,20 @@ export class TerminalInstance implements ITerminalInstance { } else { scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio) + font.letterSpacing; } - this._cols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); + const newCols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); const scaledHeightAvailable = dimension.height * window.devicePixelRatio; const scaledCharHeight = Math.ceil(font.charHeight * window.devicePixelRatio); const scaledLineHeight = Math.floor(scaledCharHeight * font.lineHeight); - this._rows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1); + const newRows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1); + + if (this._cols !== newCols || this._rows !== newRows) { + this._cols = newCols; + this._rows = newRows; + if (this.shellLaunchConfig.isRendererOnly) { + this._onMaximumDimensionsChanged.fire(); + } + } return dimension.width; } @@ -958,7 +970,7 @@ export class TerminalInstance implements ITerminalInstance { // Create the process asynchronously to allow the terminal's container // to be created so dimensions are accurate setTimeout(() => { - this._processManager!.createProcess(this._shellLaunchConfig, this._cols, this._rows); + this._processManager!.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized()); }, 0); } @@ -1238,7 +1250,6 @@ export class TerminalInstance implements ITerminalInstance { return; } - const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height); if (!terminalWidth) { return; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index dbbb9313e12..08980a94000 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { IWindowsShellHelper, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IWindowsShellHelper, ITerminalChildProcess, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; import { IProcessEnvironment } from 'vs/base/common/platform'; +import { Emitter, Event } from 'vs/base/common/event'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; @@ -17,6 +18,9 @@ let SearchAddon: typeof XTermSearchAddon; export class TerminalInstanceService implements ITerminalInstanceService { public _serviceBrand: any; + private readonly _onRequestDefaultShellAndArgs = new Emitter(); + public get onRequestDefaultShellAndArgs(): Event { return this._onRequestDefaultShellAndArgs.event; } + constructor() { } public async getXtermConstructor(): Promise { @@ -48,14 +52,11 @@ export class TerminalInstanceService implements ITerminalInstanceService { throw new Error('Not implemented'); } - public getDefaultShell(): string { - throw new Error('Not implemented'); + public getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> { + return new Promise(r => this._onRequestDefaultShellAndArgs.fire((shell, args) => r({ shell, args }))); } public async getMainProcessParentEnv(): Promise { return {}; } - - public mergeDefaultShellPathAndArgs(): void { - } } \ No newline at end of file diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index c2eae172479..a89f5bf7cdc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -137,6 +137,9 @@ export class TerminalLinkHandler { public registerWebLinkHandler(): void { this._terminalInstanceService.getXtermWebLinksConstructor().then((WebLinksAddon) => { + if (!this._xterm) { + return; + } const wrappedHandler = this._wrapLinkHandler(uri => { this._handleHypertextLink(uri); }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts index 0e2849731fc..2ed3246317c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts @@ -23,10 +23,6 @@ export class TerminalNativeService implements ITerminalNativeService { throw new Error('Not implemented'); } - public selectDefaultWindowsShell(): Promise { - throw new Error('Not implemented'); - } - public getWslPath(): Promise { throw new Error('Not implemented'); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 3eca2ccd977..2f3f3600781 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -100,7 +100,8 @@ export class TerminalProcessManager implements ITerminalProcessManager { public async createProcess( shellLaunchConfig: IShellLaunchConfig, cols: number, - rows: number + rows: number, + isScreenReaderModeEnabled: boolean ): Promise { const forceExtHostProcess = (this._configHelper.config as any).extHostProcess; if (shellLaunchConfig.cwd && typeof shellLaunchConfig.cwd === 'object') { @@ -127,7 +128,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper); } else { - this._process = await this._launchProcess(shellLaunchConfig, cols, rows); + this._process = await this._launchProcess(shellLaunchConfig, cols, rows, isScreenReaderModeEnabled); } this.processState = ProcessState.LAUNCHING; @@ -161,9 +162,16 @@ export class TerminalProcessManager implements ITerminalProcessManager { }, LAUNCHING_DURATION); } - private async _launchProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): Promise { + private async _launchProcess( + shellLaunchConfig: IShellLaunchConfig, + cols: number, + rows: number, + isScreenReaderModeEnabled: boolean + ): Promise { if (!shellLaunchConfig.executable) { - this._terminalInstanceService.mergeDefaultShellPathAndArgs(shellLaunchConfig, this._terminalInstanceService.getDefaultShell(platform.platform), this._configHelper); + const defaultConfig = await this._terminalInstanceService.getDefaultShellAndArgs(); + shellLaunchConfig.executable = defaultConfig.shell; + shellLaunchConfig.args = defaultConfig.args; } const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file); @@ -176,7 +184,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { const baseEnv = this._configHelper.config.inheritEnv ? process.env as platform.IProcessEnvironment : await this._terminalInstanceService.getMainProcessParentEnv(); const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables, baseEnv); - const useConpty = this._configHelper.config.windowsEnableConpty; + const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled; return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 00c2820793d..1b117021f53 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -21,6 +21,8 @@ import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TerminalService extends CommonTerminalService implements ITerminalService { private _configHelper: IBrowserTerminalConfigHelper; @@ -39,9 +41,11 @@ export class TerminalService extends CommonTerminalService implements ITerminalS @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @ITerminalNativeService readonly terminalNativeService: ITerminalNativeService + @ITerminalNativeService readonly terminalNativeService: ITerminalNativeService, + @IQuickInputService readonly quickInputService: IQuickInputService, + @IConfigurationService readonly configurationService: IConfigurationService ) { - super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService, terminalNativeService); + super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService, terminalNativeService, quickInputService, configurationService); this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this.terminalNativeService.linuxDistro); } @@ -52,7 +56,7 @@ export class TerminalService extends CommonTerminalService implements ITerminalS } public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance { - if (shell.runInBackground) { + if (shell.hideFromUser) { const instance = this.createInstance(this._terminalFocusContextKey, this.configHelper, undefined, @@ -82,7 +86,7 @@ export class TerminalService extends CommonTerminalService implements ITerminalS protected _showBackgroundTerminal(instance: ITerminalInstance): void { this._backgroundedTerminalInstances.splice(this._backgroundedTerminalInstances.indexOf(instance), 1); - instance.shellLaunchConfig.runInBackground = false; + instance.shellLaunchConfig.hideFromUser = false; const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalFocusContextKey, this.configHelper, diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index c66a35797f0..7f9eb86c400 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -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 { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { RawContextKey, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -56,6 +57,8 @@ export const TerminalCursorStyle = { export const TERMINAL_CONFIG_SECTION = 'terminal.integrated'; +export const TERMINAL_ACTION_CATEGORY = nls.localize('terminalCategory', "Terminal"); + export const DEFAULT_LETTER_SPACING = 0; export const MINIMUM_LETTER_SPACING = -5; export const DEFAULT_LINE_HEIGHT = 1; @@ -201,7 +204,7 @@ export interface IShellLaunchConfig { * interaction is needed. Note that the terminals will still be exposed to all extensions * as normal. */ - runInBackground?: boolean; + hideFromUser?: boolean; } export interface ITerminalService { @@ -218,10 +221,12 @@ export interface ITerminalService { onInstanceDisposed: Event; onInstanceProcessIdReady: Event; onInstanceDimensionsChanged: Event; + onInstanceMaximumDimensionsChanged: Event; onInstanceRequestExtHostProcess: Event; onInstancesChanged: Event; onInstanceTitleChanged: Event; onActiveInstanceChanged: Event; + onRequestAvailableShells: Event; /** * Creates a terminal. @@ -239,7 +244,7 @@ export interface ITerminalService { * Creates a raw terminal instance, this should not be used outside of the terminal part. */ createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance; - getInstanceFromId(terminalId: number): ITerminalInstance; + getInstanceFromId(terminalId: number): ITerminalInstance | undefined; getInstanceFromIndex(terminalIndex: number): ITerminalInstance; getTabLabels(): string[]; getActiveInstance(): ITerminalInstance | null; @@ -267,6 +272,8 @@ export interface ITerminalService { findNext(): void; findPrevious(): void; + selectDefaultWindowsShell(): Promise; + setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; setWorkspaceShellAllowed(isAllowed: boolean): void; @@ -299,7 +306,11 @@ export interface ITerminalNativeService { getWindowsBuildNumber(): number; whenFileDeleted(path: URI): Promise; getWslPath(path: string): Promise; - selectDefaultWindowsShell(): Promise; +} + +export interface IShellDefinition { + label: string; + path: string; } export const enum Direction { @@ -367,6 +378,8 @@ export interface ITerminalInstance { readonly cols: number; readonly rows: number; + readonly maxCols: number; + readonly maxRows: number; /** * The process ID of the shell process, this is undefined when there is no process associated @@ -385,12 +398,10 @@ export interface ITerminalInstance { onDisposed: Event; onFocused: Event; - onProcessIdReady: Event; - onRequestExtHostProcess: Event; - onDimensionsChanged: Event; + onMaximumDimensionsChanged: Event; onFocus: Event; @@ -690,7 +701,7 @@ export interface ITerminalProcessManager extends IDisposable { readonly onProcessExit: Event; dispose(immediate?: boolean): void; - createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): Promise; + createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise; write(data: string): void; setDimensions(cols: number, rows: number): void; @@ -746,6 +757,14 @@ export interface ITerminalProcessExtHostRequest { isWorkspaceShellAllowed: boolean; } +export interface IAvailableShellsRequest { + (shells: IShellDefinition[]): void; +} + +export interface IDefaultShellAndArgsRequest { + (shell: string, args: string[] | string | undefined): void; +} + export enum LinuxDistro { Fedora, Ubuntu, @@ -779,4 +798,4 @@ export interface ITerminalChildProcess { getInitialCwd(): Promise; getCwd(): Promise; getLatency(): Promise; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/common/terminalCommands.ts b/src/vs/workbench/contrib/terminal/common/terminalCommands.ts index 92580041923..8dd482ffbab 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalCommands.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalCommands.ts @@ -22,6 +22,7 @@ export const enum TERMINAL_COMMAND_ID { MOVE_TO_LINE_START = 'workbench.action.terminal.moveToLineStart', MOVE_TO_LINE_END = 'workbench.action.terminal.moveToLineEnd', NEW = 'workbench.action.terminal.new', + NEW_LOCAL = 'workbench.action.terminal.newLocal', NEW_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.newInActiveWorkspace', SPLIT = 'workbench.action.terminal.split', SPLIT_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.splitInActiveWorkspace', diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 78a6f0e734f..7309615c7fd 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -192,7 +192,7 @@ export function getDefaultShell( return executable; } -function getDefaultShellArgs( +export function getDefaultShellArgs( fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, isWorkspaceShellAllowed: boolean, platformOverride: platform.Platform = platform.platform @@ -203,19 +203,6 @@ function getDefaultShellArgs( return args; } -export function mergeDefaultShellPathAndArgs( - shell: IShellLaunchConfig, - fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, - isWorkspaceShellAllowed: boolean, - defaultShell: string, - isWoW64: boolean, - windir: string | undefined, - platformOverride: platform.Platform = platform.platform -): void { - shell.executable = getDefaultShell(fetchSetting, isWorkspaceShellAllowed, defaultShell, isWoW64, windir, platformOverride); - shell.args = getDefaultShellArgs(fetchSetting, isWorkspaceShellAllowed, platformOverride); -} - export function createTerminalEnvironment( shellLaunchConfig: IShellLaunchConfig, lastActiveWorkspace: IWorkspaceFolder | null, diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index c7da0cb4dbc..a5d78c06cf5 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalNativeService, IShellDefinition, IAvailableShellsRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; @@ -17,11 +17,13 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform'; import { basename } from 'vs/base/common/path'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { timeout } from 'vs/base/common/async'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; +import { IPickOptions, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export abstract class TerminalService implements ITerminalService { public _serviceBrand: any; @@ -55,6 +57,8 @@ export abstract class TerminalService implements ITerminalService { public get onInstanceRequestExtHostProcess(): Event { return this._onInstanceRequestExtHostProcess.event; } protected readonly _onInstanceDimensionsChanged = new Emitter(); public get onInstanceDimensionsChanged(): Event { return this._onInstanceDimensionsChanged.event; } + protected readonly _onInstanceMaximumDimensionsChanged = new Emitter(); + public get onInstanceMaximumDimensionsChanged(): Event { return this._onInstanceMaximumDimensionsChanged.event; } protected readonly _onInstancesChanged = new Emitter(); public get onInstancesChanged(): Event { return this._onInstancesChanged.event; } protected readonly _onInstanceTitleChanged = new Emitter(); @@ -63,6 +67,8 @@ export abstract class TerminalService implements ITerminalService { public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } protected readonly _onTabDisposed = new Emitter(); public get onTabDisposed(): Event { return this._onTabDisposed.event; } + protected readonly _onRequestAvailableShells = new Emitter(); + public get onRequestAvailableShells(): Event { return this._onRequestAvailableShells.event; } public abstract get configHelper(): ITerminalConfigHelper; @@ -76,7 +82,9 @@ export abstract class TerminalService implements ITerminalService { @IExtensionService private readonly _extensionService: IExtensionService, @IFileService protected readonly _fileService: IFileService, @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService, - @ITerminalNativeService private readonly _terminalNativeService: ITerminalNativeService + @ITerminalNativeService private readonly _terminalNativeService: ITerminalNativeService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { this._activeTabIndex = 0; this._isShuttingDown = false; @@ -252,7 +260,7 @@ export abstract class TerminalService implements ITerminalService { return tab.activeInstance; } - public getInstanceFromId(terminalId: number): ITerminalInstance { + public getInstanceFromId(terminalId: number): ITerminalInstance | undefined { let bgIndex = -1; this._backgroundedTerminalInstances.forEach((terminalInstance, i) => { if (terminalInstance.id === terminalId) { @@ -262,7 +270,11 @@ export abstract class TerminalService implements ITerminalService { if (bgIndex !== -1) { return this._backgroundedTerminalInstances[bgIndex]; } - return this.terminalInstances[this._getIndexFromId(terminalId)]; + try { + return this.terminalInstances[this._getIndexFromId(terminalId)]; + } catch { + return undefined; + } } public getInstanceFromIndex(terminalIndex: number): ITerminalInstance { @@ -270,9 +282,9 @@ export abstract class TerminalService implements ITerminalService { } public setActiveInstance(terminalInstance: ITerminalInstance): void { - // If this was a runInBackground terminal created by the API this was triggered by show, + // If this was a hideFromUser terminal created by the API this was triggered by show, // in which case we need to create the terminal tab - if (terminalInstance.shellLaunchConfig.runInBackground) { + if (terminalInstance.shellLaunchConfig.hideFromUser) { this._showBackgroundTerminal(terminalInstance); } this.setActiveInstanceByIndex(this._getIndexFromId(terminalInstance.id)); @@ -374,6 +386,7 @@ export abstract class TerminalService implements ITerminalService { instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance))); + instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance))); instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged)); } @@ -522,4 +535,34 @@ export abstract class TerminalService implements ITerminalService { c(escapeNonWindowsPath(originalPath)); }); } + + public selectDefaultWindowsShell(): Promise { + return this._detectWindowsShells().then(shells => { + const options: IPickOptions = { + placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") + }; + const quickPickItems = shells.map(s => { + return { label: s.label, description: s.path }; + }); + return this._quickInputService.pick(quickPickItems, options).then(async value => { + if (!value) { + return undefined; + } + const shell = value.description; + const env = await this._remoteAgentService.getEnvironment(); + let platformKey: string; + if (env) { + platformKey = env.os === OperatingSystem.Windows ? 'windows' : (env.os === OperatingSystem.Macintosh ? 'osx' : 'linux'); + } else { + platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux'); + } + await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, shell, ConfigurationTarget.USER).then(() => shell); + return Promise.resolve(); + }); + }); + } + + private _detectWindowsShells(): Promise { + return new Promise(r => this._onRequestAvailableShells.fire(r)); + } } diff --git a/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts b/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts index 6867fd01871..c08a6f47fe8 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts @@ -8,7 +8,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { Registry } from 'vs/platform/registry/common/platform'; import { Platform } from 'vs/base/common/platform'; -export function registerShellConfiguration(getDefaultShell?: (p: Platform) => string): void { +export function registerShellConfiguration(getSystemShell?: (p: Platform) => string): void { const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ id: 'terminal', @@ -18,24 +18,24 @@ export function registerShellConfiguration(getDefaultShell?: (p: Platform) => st properties: { 'terminal.integrated.shell.linux': { markdownDescription: - getDefaultShell - ? nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(Platform.Linux)) + getSystemShell + ? nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Linux)) : nls.localize('terminal.integrated.shell.linux.noDefault', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: ['string', 'null'], default: null }, 'terminal.integrated.shell.osx': { markdownDescription: - getDefaultShell - ? nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(Platform.Mac)) + getSystemShell + ? nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Mac)) : nls.localize('terminal.integrated.shell.osx.noDefault', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: ['string', 'null'], default: null }, 'terminal.integrated.shell.windows': { markdownDescription: - getDefaultShell - ? nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(Platform.Windows)) + getSystemShell + ? nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getSystemShell(Platform.Windows)) : nls.localize('terminal.integrated.shell.windows.noDefault', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: ['string', 'null'], default: null diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts index 9937eea4c5b..f9dd46267ca 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts @@ -6,11 +6,11 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/electron-browser/terminalInstanceService'; -import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; +import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal'; import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig'; import { TerminalNativeService } from 'vs/workbench/contrib/terminal/electron-browser/terminalNativeService'; import { ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; -registerShellConfiguration(getDefaultShell); +registerShellConfiguration(getSystemShell); registerSingleton(ITerminalNativeService, TerminalNativeService, true); registerSingleton(ITerminalInstanceService, TerminalInstanceService, true); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 357bd5dc736..94b5a713671 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -4,19 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ITerminalInstance, IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalInstance, IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal'; import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/node/windowsShellHelper'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProcessEnvironment, Platform, isLinux, isMacintosh, isWindows, OperatingSystem, platform } from 'vs/base/common/platform'; +import { IProcessEnvironment, platform, Platform } from 'vs/base/common/platform'; import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; -import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; +import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal'; import { Terminal as XTermTerminal } from 'xterm'; import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; -import { readFile } from 'vs/base/node/pfs'; -import { basename } from 'vs/base/common/path'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { mergeDefaultShellPathAndArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; +import { getDefaultShell, getDefaultShellArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; +import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; @@ -25,11 +25,10 @@ let SearchAddon: typeof XTermSearchAddon; export class TerminalInstanceService implements ITerminalInstanceService { public _serviceBrand: any; - private _mainProcessParentEnv: IProcessEnvironment | undefined; - constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IStorageService private readonly _storageService: IStorageService ) { } @@ -62,89 +61,29 @@ export class TerminalInstanceService implements ITerminalInstanceService { return this._instantiationService.createInstance(TerminalProcess, shellLaunchConfig, cwd, cols, rows, env, windowsEnableConpty); } - public getDefaultShell(p: Platform): string { - return getDefaultShell(p); + private _isWorkspaceShellAllowed(): boolean { + return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, false); } - public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, defaultShell: string, configHelper: ITerminalConfigHelper, platformOverride: Platform = platform): void { - const isWorkspaceShellAllowed = configHelper.checkWorkspaceShellPermissions(platformOverride === Platform.Windows ? OperatingSystem.Windows : (platformOverride === Platform.Mac ? OperatingSystem.Macintosh : OperatingSystem.Linux)); - mergeDefaultShellPathAndArgs( - shell, + public getDefaultShellAndArgs(platformOverride: Platform = platform): Promise<{ shell: string, args: string[] | undefined }> { + const isWorkspaceShellAllowed = this._isWorkspaceShellAllowed(); + const shell = getDefaultShell( (key) => this._configurationService.inspect(key), isWorkspaceShellAllowed, - defaultShell, + getSystemShell(platformOverride), process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), process.env.windir, platformOverride ); + const args = getDefaultShellArgs( + (key) => this._configurationService.inspect(key), + isWorkspaceShellAllowed, + platformOverride + ); + return Promise.resolve({ shell, args }); } - public async getMainProcessParentEnv(): Promise { - if (this._mainProcessParentEnv) { - return this._mainProcessParentEnv; - } - - // For Linux use /proc//status to get the parent of the main process and then fetch its - // env using /proc//environ. - if (isLinux) { - const mainProcessId = process.ppid; - const codeProcessName = basename(process.argv[0]); - let pid: number = 0; - let ppid: number = mainProcessId; - let name: string = codeProcessName; - do { - pid = ppid; - const status = await readFile(`/proc/${pid}/status`, 'utf8'); - const splitByLine = status.split('\n'); - splitByLine.forEach(line => { - if (line.indexOf('Name:') === 0) { - name = line.replace(/^Name:\s+/, ''); - } - if (line.indexOf('PPid:') === 0) { - ppid = parseInt(line.replace(/^PPid:\s+/, '')); - } - }); - } while (name === codeProcessName); - const rawEnv = await readFile(`/proc/${pid}/environ`, 'utf8'); - const env = {}; - rawEnv.split('\0').forEach(e => { - const i = e.indexOf('='); - env[e.substr(0, i)] = e.substr(i + 1); - }); - this._mainProcessParentEnv = env; - } - - // For macOS we want the "root" environment as shells by default run as login shells. It - // doesn't appear to be possible to get the "root" environment as `ps eww -o command` for - // PID 1 (the parent of the main process when launched from the dock/finder) returns no - // environment, because of this we will fill in the root environment using a whitelist of - // environment variables that we have. - if (isMacintosh) { - this._mainProcessParentEnv = {}; - // This list was generated by diffing launching a terminal with {} and the system - // terminal launched from finder. - const rootEnvVars = [ - 'SHELL', - 'SSH_AUTH_SOCK', - 'Apple_PubSub_Socket_Render', - 'XPC_FLAGS', - 'XPC_SERVICE_NAME', - 'HOME', - 'LOGNAME', - 'TMPDIR' - ]; - rootEnvVars.forEach(k => { - if (process.env[k]) { - this._mainProcessParentEnv![k] = process.env[k]!; - } - }); - } - - // TODO: Windows should return a fresh environment block, might need native code? - if (isWindows) { - this._mainProcessParentEnv = process.env as IProcessEnvironment; - } - - return this._mainProcessParentEnv!; + public getMainProcessParentEnv(): Promise { + return getMainProcessParentEnv(); } } \ No newline at end of file diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts index 68b7462447a..d8591bc6172 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeService.ts @@ -3,20 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { ipcRenderer as ipc } from 'electron'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { getWindowsBuildNumber, linuxDistro } from 'vs/workbench/contrib/terminal/node/terminal'; -import { IQuickPickItem, IPickOptions, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { execFile } from 'child_process'; -import { coalesce } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { registerRemoteContributions } from 'vs/workbench/contrib/terminal/node/terminalRemote'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class TerminalNativeService implements ITerminalNativeService { public _serviceBrand: any; @@ -30,12 +28,16 @@ export class TerminalNativeService implements ITerminalNativeService { constructor( @IFileService private readonly _fileService: IFileService, - @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService readonly instantiationService: IInstantiationService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService ) { ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => this._onOpenFileRequest.fire(request)); ipc.on('vscode:osResume', () => this._onOsResume.fire()); + + const connection = remoteAgentService.getConnection(); + if (connection && connection.remoteAuthority) { + registerRemoteContributions(); + } } public whenFileDeleted(path: URI): Promise { @@ -58,99 +60,6 @@ export class TerminalNativeService implements ITerminalNativeService { }); } - public selectDefaultWindowsShell(): Promise { - return this._detectWindowsShells().then(shells => { - const options: IPickOptions = { - placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") - }; - return this._quickInputService.pick(shells, options).then(value => { - if (!value) { - return undefined; - } - const shell = value.description; - return this._configurationService.updateValue('terminal.integrated.shell.windows', shell, ConfigurationTarget.USER).then(() => shell); - }); - }); - } - - /** - * Get the executable file path of shell from registry. - * @param shellName The shell name to get the executable file path - * @returns `[]` or `[ 'path' ]` - */ - private async _getShellPathFromRegistry(shellName: string): Promise { - const Registry = await import('vscode-windows-registry'); - - try { - const shellPath = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${shellName}.exe`, ''); - - if (shellPath === undefined) { - return []; - } - - return [shellPath]; - } catch (error) { - return []; - } - } - - private async _detectWindowsShells(): Promise { - // Determine the correct System32 path. We want to point to Sysnative - // when the 32-bit version of VS Code is running on a 64-bit machine. - // The reason for this is because PowerShell's important PSReadline - // module doesn't work if this is not the case. See #27915. - const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`; - - let useWSLexe = false; - - if (getWindowsBuildNumber() >= 16299) { - useWSLexe = true; - } - - const expectedLocations = { - 'Command Prompt': [`${system32Path}\\cmd.exe`], - PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], - 'PowerShell Core': await this._getShellPathFromRegistry('pwsh'), - 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], - 'Git Bash': [ - `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, - `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, - `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, - `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, - `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, - ] - }; - const promises: PromiseLike<[string, string]>[] = []; - Object.keys(expectedLocations).forEach(key => promises.push(this._validateShellPaths(key, expectedLocations[key]))); - return Promise.all(promises) - .then(coalesce) - .then(results => { - return results.map(result => { - return { - label: result[0], - description: result[1] - }; - }); - }); - } - - private _validateShellPaths(label: string, potentialPaths: string[]): Promise<[string, string] | null> { - if (potentialPaths.length === 0) { - return Promise.resolve(null); - } - const current = potentialPaths.shift(); - if (current! === '') { - return this._validateShellPaths(label, potentialPaths); - } - return this._fileService.exists(URI.file(current!)).then(exists => { - if (!exists) { - return this._validateShellPaths(label, potentialPaths); - } - return [label, current] as [string, string]; - }); - } - /** * Converts a path to a path on WSL using the wslpath utility. * @param path The original path. diff --git a/src/vs/workbench/contrib/terminal/node/terminal.ts b/src/vs/workbench/contrib/terminal/node/terminal.ts index 493fad0b833..1f8eeafa076 100644 --- a/src/vs/workbench/contrib/terminal/node/terminal.ts +++ b/src/vs/workbench/contrib/terminal/node/terminal.ts @@ -6,13 +6,20 @@ import * as os from 'os'; import * as platform from 'vs/base/common/platform'; import * as processes from 'vs/base/node/processes'; -import { readFile, fileExists } from 'vs/base/node/pfs'; -import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { readFile, fileExists, stat } from 'vs/base/node/pfs'; +import { LinuxDistro, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal'; +import { coalesce } from 'vs/base/common/arrays'; +import { normalize, basename } from 'vs/base/common/path'; -export function getDefaultShell(p: platform.Platform): string { +/** + * Gets the detected default shell for the _system_, not to be confused with VS Code's _default_ + * shell that the terminal uses by default. + * @param p The platform to detect the shell of. + */ +export function getSystemShell(p: platform.Platform): string { if (p === platform.Platform.Windows) { if (platform.isWindows) { - return getTerminalDefaultShellWindows(); + return getSystemShellWindows(); } // Don't detect Windows shell when not on Windows return processes.getWindowsShell(); @@ -21,11 +28,11 @@ export function getDefaultShell(p: platform.Platform): string { if (platform.isLinux && p === platform.Platform.Mac || platform.isMacintosh && p === platform.Platform.Linux) { return '/bin/bash'; } - return getTerminalDefaultShellUnixLike(); + return getSystemShellUnixLike(); } let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null; -function getTerminalDefaultShellUnixLike(): string { +function getSystemShellUnixLike(): string { if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) { let unixLikeTerminal = 'sh'; if (!platform.isWindows && process.env.SHELL) { @@ -44,7 +51,7 @@ function getTerminalDefaultShellUnixLike(): string { } let _TERMINAL_DEFAULT_SHELL_WINDOWS: string | null = null; -function getTerminalDefaultShellWindows(): string { +function getSystemShellWindows(): string { if (!_TERMINAL_DEFAULT_SHELL_WINDOWS) { const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10; const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); @@ -82,3 +89,85 @@ export function getWindowsBuildNumber(): number { } return buildNumber; } + +export function detectAvailableShells(): Promise { + return platform.isWindows ? detectAvailableWindowsShells() : detectAvailableUnixShells(); +} + +async function detectAvailableWindowsShells(): Promise { + // Determine the correct System32 path. We want to point to Sysnative + // when the 32-bit version of VS Code is running on a 64-bit machine. + // The reason for this is because PowerShell's important PSReadline + // module doesn't work if this is not the case. See #27915. + const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); + const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`; + + let useWSLexe = false; + + if (getWindowsBuildNumber() >= 16299) { + useWSLexe = true; + } + + const expectedLocations = { + 'Command Prompt': [`${system32Path}\\cmd.exe`], + PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], + 'PowerShell Core': [await getShellPathFromRegistry('pwsh')], + 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], + 'Git Bash': [ + `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, + `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, + `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, + ], + Cygwin: [ + `${process.env['HOMEDRIVE']}\\cygwin64\\bin\\bash.exe`, + `${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe` + ] + }; + const promises: PromiseLike[] = []; + Object.keys(expectedLocations).forEach(key => promises.push(validateShellPaths(key, expectedLocations[key]))); + + return Promise.all(promises).then(coalesce); +} + +async function detectAvailableUnixShells(): Promise { + const contents = await readFile('/etc/shells', 'utf8'); + const shells = contents.split('\n').filter(e => e.trim().indexOf('#') !== 0 && e.trim().length > 0); + return shells.map(e => { + return { + label: basename(e), + path: e + }; + }); +} + +async function validateShellPaths(label: string, potentialPaths: string[]): Promise { + if (potentialPaths.length === 0) { + return Promise.resolve(undefined); + } + const current = potentialPaths.shift()!; + if (current! === '') { + return validateShellPaths(label, potentialPaths); + } + try { + const result = await stat(normalize(current)); + if (result.isFile || result.isSymbolicLink) { + return { + label, + path: current + }; + } + } catch { /* noop */ } + return validateShellPaths(label, potentialPaths); +} + +async function getShellPathFromRegistry(shellName: string): Promise { + const Registry = await import('vscode-windows-registry'); + try { + const shellPath = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${shellName}.exe`, ''); + return shellPath ? shellPath : ''; + } catch (error) { + return ''; + } +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts new file mode 100644 index 00000000000..1f8a127dbae --- /dev/null +++ b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { readFile } from 'vs/base/node/pfs'; +import { basename } from 'vs/base/common/path'; + +let mainProcessParentEnv: IProcessEnvironment | undefined; + +export async function getMainProcessParentEnv(): Promise { + if (mainProcessParentEnv) { + return mainProcessParentEnv; + } + + // For Linux use /proc//status to get the parent of the main process and then fetch its + // env using /proc//environ. + if (isLinux) { + const mainProcessId = process.ppid; + const codeProcessName = basename(process.argv[0]); + let pid: number = 0; + let ppid: number = mainProcessId; + let name: string = codeProcessName; + do { + pid = ppid; + const status = await readFile(`/proc/${pid}/status`, 'utf8'); + const splitByLine = status.split('\n'); + splitByLine.forEach(line => { + if (line.indexOf('Name:') === 0) { + name = line.replace(/^Name:\s+/, ''); + } + if (line.indexOf('PPid:') === 0) { + ppid = parseInt(line.replace(/^PPid:\s+/, '')); + } + }); + } while (name === codeProcessName); + const rawEnv = await readFile(`/proc/${pid}/environ`, 'utf8'); + const env = {}; + rawEnv.split('\0').forEach(e => { + const i = e.indexOf('='); + env[e.substr(0, i)] = e.substr(i + 1); + }); + mainProcessParentEnv = env; + } + + // For macOS we want the "root" environment as shells by default run as login shells. It + // doesn't appear to be possible to get the "root" environment as `ps eww -o command` for + // PID 1 (the parent of the main process when launched from the dock/finder) returns no + // environment, because of this we will fill in the root environment using a whitelist of + // environment variables that we have. + if (isMacintosh) { + mainProcessParentEnv = {}; + // This list was generated by diffing launching a terminal with {} and the system + // terminal launched from finder. + const rootEnvVars = [ + 'SHELL', + 'SSH_AUTH_SOCK', + 'Apple_PubSub_Socket_Render', + 'XPC_FLAGS', + 'XPC_SERVICE_NAME', + 'HOME', + 'LOGNAME', + 'TMPDIR' + ]; + rootEnvVars.forEach(k => { + if (process.env[k]) { + mainProcessParentEnv![k] = process.env[k]!; + } + }); + } + + // TODO: Windows should return a fresh environment block, might need native code? + if (isWindows) { + mainProcessParentEnv = process.env as IProcessEnvironment; + } + + return mainProcessParentEnv!; +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/terminal/node/terminalRemote.ts b/src/vs/workbench/contrib/terminal/node/terminalRemote.ts new file mode 100644 index 00000000000..e264cdfdd5d --- /dev/null +++ b/src/vs/workbench/contrib/terminal/node/terminalRemote.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { TERMINAL_ACTION_CATEGORY, ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; +import { Action } from 'vs/base/common/actions'; +import { URI } from 'vs/base/common/uri'; +import { homedir } from 'os'; + +export function registerRemoteContributions() { + const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); + actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewLocalTerminalAction, CreateNewLocalTerminalAction.ID, CreateNewLocalTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (Local)', TERMINAL_ACTION_CATEGORY); +} + +export class CreateNewLocalTerminalAction extends Action { + public static readonly ID = TERMINAL_COMMAND_ID.NEW_LOCAL; + public static readonly LABEL = nls.localize('workbench.action.terminal.newLocal', "Create New Integrated Terminal (Local)"); + + constructor( + id: string, label: string, + @ITerminalService private readonly terminalService: ITerminalService + ) { + super(id, label); + } + + public run(event?: any): Promise { + const instance = this.terminalService.createTerminal({ cwd: URI.file(homedir()) }); + if (!instance) { + return Promise.resolve(undefined); + } + + // Append (Local) to the first title that comes back, the title will then become static + const disposable = instance.onTitleChanged(() => { + if (instance.title && instance.title.trim().length > 0) { + disposable.dispose(); + instance.setTitle(`${instance.title} (Local)`, false); + } + }); + + this.terminalService.setActiveInstance(instance); + return this.terminalService.showPanel(true); + } +} diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts index e0110601c82..f1984496f23 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts @@ -20,23 +20,34 @@ suite('Workbench - TerminalConfigHelper', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: 'bar' } }); - - let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!); + const configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, 'bar', 'terminal.integrated.fontFamily should be selected over editor.fontFamily'); + }); + test('TerminalConfigHelper - getFont fontFamily (Linux Fedora)', function () { + const configurationService = new TestConfigurationService(); + configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); - - // Recreate config helper as onDidChangeConfiguration isn't implemented in TestConfigurationService - configHelper = new TerminalConfigHelper(LinuxDistro.Fedora, configurationService, null!, null!, null!); + const configHelper = new TerminalConfigHelper(LinuxDistro.Fedora, configurationService, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, '\'DejaVu Sans Mono\', monospace', 'Fedora should have its font overridden when terminal.integrated.fontFamily not set'); + }); - configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!); + test('TerminalConfigHelper - getFont fontFamily (Linux Ubuntu)', function () { + const configurationService = new TestConfigurationService(); + configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); + configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); + const configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, '\'Ubuntu Mono\', monospace', 'Ubuntu should have its font overridden when terminal.integrated.fontFamily not set'); + }); - configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!); + test('TerminalConfigHelper - getFont fontFamily (Linux Unknown)', function () { + const configurationService = new TestConfigurationService(); + configurationService.setUserConfiguration('editor', { fontFamily: 'foo' }); + configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } }); + const configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!); configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontFamily, 'foo', 'editor.fontFamily should be the fallback when terminal.integrated.fontFamily not set'); }); diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts index d6abc8d9ccf..96daea4ad44 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Platform, OperatingSystem } from 'vs/base/common/platform'; +import { OperatingSystem } from 'vs/base/common/platform'; import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import * as strings from 'vs/base/common/strings'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { Event } from 'vs/base/common/event'; class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { @@ -30,7 +31,8 @@ class TestXterm { } class MockTerminalInstanceService implements ITerminalInstanceService { - mergeDefaultShellPathAndArgs(): void { + onRequestDefaultShellAndArgs?: Event | undefined; + getDefaultShellAndArgs(): Promise<{ shell: string; args: string | string[] | undefined; }> { throw new Error('Method not implemented.'); } _serviceBrand: any; @@ -49,9 +51,6 @@ class MockTerminalInstanceService implements ITerminalInstanceService { createTerminalProcess(): any { throw new Error('Method not implemented.'); } - getDefaultShell(p: Platform): string { - throw new Error('Method not implemented.'); - } getMainProcessParentEnv(): any { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/webview/browser/pre/host.js b/src/vs/workbench/contrib/webview/browser/pre/host.js new file mode 100644 index 00000000000..3ffd33bdb72 --- /dev/null +++ b/src/vs/workbench/contrib/webview/browser/pre/host.js @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +(function () { + const id = document.location.search.match(/\bid=([\w-]+)/)[1]; + + const hostMessaging = new class HostMessaging { + constructor() { + this.handlers = new Map(); + window.addEventListener('message', (e) => { + if (e.data && (e.data.command === 'onmessage' || e.data.command === 'do-update-state')) { + // Came from inner iframe + this.postMessage(e.data.command, e.data.data); + return; + } + + const channel = e.data.channel; + const handler = this.handlers.get(channel); + if (handler) { + handler(e, e.data.args); + } else { + console.log('no handler for ', e); + } + }); + } + + postMessage(channel, data) { + window.parent.postMessage({ target: id, channel, data }, '*'); + } + + onMessage(channel, handler) { + this.handlers.set(channel, handler); + } + }(); + + const workerReady = new Promise(async (resolveWorkerReady) => { + if (!navigator.serviceWorker) { + return resolveWorkerReady(); + } + + const expectedWorkerVersion = 1; + + navigator.serviceWorker.register('service-worker.js').then(async registration => { + await navigator.serviceWorker.ready; + + const versionHandler = (event) => { + if (event.data.channel !== 'version') { + return; + } + + navigator.serviceWorker.removeEventListener('message', versionHandler); + if (event.data.version === expectedWorkerVersion) { + return resolveWorkerReady(); + } else { + // If we have the wrong version, try once to unregister and re-register + return registration.update() + .then(() => navigator.serviceWorker.ready) + .finally(resolveWorkerReady); + } + }; + navigator.serviceWorker.addEventListener('message', versionHandler); + registration.active.postMessage({ channel: 'version' }); + }); + + const forwardFromHostToWorker = (channel) => { + hostMessaging.onMessage(channel, event => { + navigator.serviceWorker.ready.then(registration => { + registration.active.postMessage({ channel: channel, data: event.data.args }); + }); + }); + }; + forwardFromHostToWorker('did-load-resource'); + forwardFromHostToWorker('did-load-localhost'); + + navigator.serviceWorker.addEventListener('message', event => { + if (['load-resource', 'load-localhost'].includes(event.data.channel)) { + hostMessaging.postMessage(event.data.channel, event.data); + } + }); + }); + + window.createWebviewManager({ + postMessage: hostMessaging.postMessage.bind(hostMessaging), + onMessage: hostMessaging.onMessage.bind(hostMessaging), + ready: workerReady, + }); +}()); \ No newline at end of file diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 4b247552399..03337a5f6af 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -1,5 +1,5 @@ - + @@ -11,46 +11,9 @@ Virtual Document - + - + - + + \ No newline at end of file diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 2b706c812da..a6be033e074 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -99,8 +99,8 @@ * postMessage: (channel: string, data?: any) => void, * onMessage: (channel: string, handler: any) => void, * injectHtml?: (document: HTMLDocument) => void, - * preProcessHtml?: (text: string) => void, - * focusIframeOnCreate?: boolean + * focusIframeOnCreate?: boolean, + * ready?: Promise * }} HostCommunications */ @@ -120,25 +120,6 @@ // Service worker for resource loading const FAKE_LOAD = !!navigator.serviceWorker; - if (navigator.serviceWorker) { - navigator.serviceWorker.register('service-worker.js'); - - navigator.serviceWorker.ready.then(registration => { - registration.active.postMessage('ping'); - - host.onMessage('loaded-resource', event => { - registration.active.postMessage({ channel: 'loaded-resource', data: event.data.args }); - }); - }); - - navigator.serviceWorker.addEventListener('message', event => { - switch (event.data.channel) { - case 'load-resource': - host.postMessage('load-resource', { path: event.data.path }); - return; - } - }); - } /** * @param {HTMLDocument?} document @@ -233,6 +214,8 @@ }; document.addEventListener('DOMContentLoaded', () => { + const idMatch = document.location.search.match(/\bid=([\w-]+)/); + const ID = idMatch ? idMatch[1] : undefined; if (!document.body) { return; } @@ -259,11 +242,19 @@ } }); + // update iframe-contents - host.onMessage('content', (_event, data) => { + let updateId = 0; + host.onMessage('content', async (_event, data) => { + const currentUpdateId = ++updateId; + await host.ready; + if (currentUpdateId !== updateId) { + return; + } + const options = data.options; - const text = host.preProcessHtml ? host.preProcessHtml(data.contents) : data.contents; + const text = data.contents; const newDocument = new DOMParser().parseFromString(text, 'text/html'); newDocument.querySelectorAll('a').forEach(a => { @@ -364,7 +355,7 @@ // seeing the service worker applying properly. // Fake load an empty on the correct origin and then write real html // into it to get around this. - newFrame.src = '/fake.html'; + newFrame.src = `/fake.html?id=${ID}`; } newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; document.body.appendChild(newFrame); @@ -379,9 +370,9 @@ newFrame.contentWindow.addEventListener('DOMContentLoaded', e => { if (FAKE_LOAD) { newFrame.contentDocument.open(); - newFrame.contentDocument.write(''); - newFrame.contentDocument.write(newDocument.documentElement.innerHTML); + newFrame.contentDocument.write('\n' + newDocument.documentElement.outerHTML); newFrame.contentDocument.close(); + hookupOnLoadHandlers(newFrame); } const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; if (contentDocument) { @@ -389,17 +380,6 @@ } }); - newFrame.contentWindow.onbeforeunload = () => { - if (isInDevelopmentMode) { // Allow reloads while developing a webview - host.postMessage('do-reload'); - return false; - } - - // Block navigation when not in development mode - console.log('prevented webview navigation'); - return false; - }; - const onLoad = (contentDocument, contentWindow) => { if (contentDocument && contentDocument.body) { // Workaround for https://github.com/Microsoft/vscode/issues/12865 @@ -430,24 +410,69 @@ } }; - clearTimeout(loadTimeout); - loadTimeout = undefined; - loadTimeout = setTimeout(() => { + /** + * @param {HTMLIFrameElement} newFrame + */ + function hookupOnLoadHandlers(newFrame) { clearTimeout(loadTimeout); loadTimeout = undefined; - onLoad(newFrame.contentDocument, newFrame.contentWindow); - }, 200); - - newFrame.contentWindow.addEventListener('load', function (e) { - if (loadTimeout) { + loadTimeout = setTimeout(() => { clearTimeout(loadTimeout); loadTimeout = undefined; - onLoad(e.target, this); - } - }); + onLoad(newFrame.contentDocument, newFrame.contentWindow); + }, 200); - // Bubble out link clicks - newFrame.contentWindow.addEventListener('click', handleInnerClick); + newFrame.contentWindow.addEventListener('load', function (e) { + if (loadTimeout) { + clearTimeout(loadTimeout); + loadTimeout = undefined; + onLoad(e.target, this); + } + }); + + if (!FAKE_LOAD) { + newFrame.contentWindow.onbeforeunload = () => { + if (isInDevelopmentMode) { // Allow reloads while developing a webview + host.postMessage('do-reload'); + return false; + } + + // Block navigation when not in development mode + console.log('prevented webview navigation'); + return false; + }; + } + + // Bubble out link clicks + newFrame.contentWindow.addEventListener('click', handleInnerClick); + + // Electron 4 eats mouseup events from inside webviews + // https://github.com/microsoft/vscode/issues/75090 + // Try to fix this by rebroadcasting mouse moves and mouseups so that we can + // emulate these on the main window + if (!FAKE_LOAD) { + let isMouseDown = false; + + newFrame.contentWindow.addEventListener('mousedown', () => { + isMouseDown = true; + }); + + const tryDispatchSyntheticMouseEvent = (e) => { + if (!isMouseDown) { + host.postMessage('synthetic-mouse-event', { type: e.type, screenX: e.screenX, screenY: e.screenY, clientX: e.clientX, clientY: e.clientY }); + } + }; + newFrame.contentWindow.addEventListener('mouseup', e => { + tryDispatchSyntheticMouseEvent(e); + isMouseDown = false; + }); + newFrame.contentWindow.addEventListener('mousemove', tryDispatchSyntheticMouseEvent); + } + } + + if (!FAKE_LOAD) { + hookupOnLoadHandlers(newFrame); + } // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden @@ -496,4 +521,4 @@ } else { window.createWebviewManager = createWebviewManager; } -}()); \ No newline at end of file +}()); diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js index 98f46d57477..23f08be0281 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js @@ -2,77 +2,165 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// Listen for messages from clients. -const resolvedPaths = new Map(); +const VERSION = 1; -self.addEventListener('message', (event) => { - switch (event.data.channel) { - case 'loaded-resource': - { - const data = event.data.data; - const target = resolvedPaths.get(data.path); - if (!target) { - console.log('Loaded unknown resource', data.path); - return; - } - - if (data.status === 200) { - target.resolve(new Response(data.data, { - status: 200, - headers: { 'Content-Type': data.mime }, - }).clone()); - } else { - target.resolve(new Response('Not Found', { - status: 404, - }).clone()); - } - } - return; - } -}); - -var clients; +/** + * Root path for resources + */ const resourceRoot = '/vscode-resource'; +const resolveTimeout = 30000; + +/** + * @template T + * @typedef {{ + * resolve: (x: T) => void, + * promise: Promise + * }} RequestStoreEntry + */ + +/** + * @template T + */ +class RequestStore { + constructor() { + /** @type {Map>} */ + this.map = new Map(); + } + + /** + * @param {string} webviewId + * @param {string} path + * @return {Promise | undefined} + */ + get(webviewId, path) { + const entry = this.map.get(this._key(webviewId, path)); + return entry && entry.promise; + } + + /** + * @param {string} webviewId + * @param {string} path + * @returns {Promise} + */ + create(webviewId, path) { + const existing = this.get(webviewId, path); + if (existing) { + return existing; + } + let resolve; + const promise = new Promise(r => resolve = r); + const entry = { resolve, promise }; + const key = this._key(webviewId, path); + this.map.set(key, entry); + + const dispose = () => { + clearTimeout(timeout); + const existingEntry = this.map.get(key); + if (existingEntry === entry) { + return this.map.delete(key); + } + }; + const timeout = setTimeout(dispose, resolveTimeout); + return promise; + } + + /** + * @param {string} webviewId + * @param {string} path + * @param {T} result + * @return {boolean} + */ + resolve(webviewId, path, result) { + const entry = this.map.get(this._key(webviewId, path)); + if (!entry) { + return false; + } + entry.resolve(result); + return true; + } + + /** + * @param {string} webviewId + * @param {string} path + * @return {string} + */ + _key(webviewId, path) { + return `${webviewId}@@@${path}`; + } +} + +/** + * Map of requested paths to responses. + * + * @type {RequestStore<{ body: any, mime: string } | undefined>} + */ +const resourceRequestStore = new RequestStore(); + +/** + * Map of requested localhost origins to optional redirects. + * + * @type {RequestStore} + */ +const localhostRequestStore = new RequestStore(); + +const notFound = () => + new Response('Not Found', { status: 404, }); + +self.addEventListener('message', async (event) => { + switch (event.data.channel) { + case 'version': + { + self.clients.get(event.source.id).then(client => { + if (client) { + client.postMessage({ + channel: 'version', + version: VERSION + }); + } + }); + return; + } + case 'did-load-resource': + { + const webviewId = getWebviewIdForClient(event.source); + const data = event.data.data; + const response = data.status === 200 + ? { body: data.data, mime: data.mime } + : undefined; + + if (!resourceRequestStore.resolve(webviewId, data.path, response)) { + console.log('Could not resolve unknown resource', data.path); + } + return; + } + + case 'did-load-localhost': + { + const webviewId = getWebviewIdForClient(event.source); + const data = event.data.data; + if (!localhostRequestStore.resolve(webviewId, data.origin, data.location)) { + console.log('Could not resolve unknown localhost', data.origin); + } + return; + } + } + + console.log('Unknown message'); +}); + self.addEventListener('fetch', (event) => { const requestUrl = new URL(event.request.url); - if (!requestUrl.pathname.startsWith(resourceRoot + '/')) { - return event.respondWith(fetch(event.request)); + // See if it's a resource request + if (requestUrl.origin === self.origin && requestUrl.pathname.startsWith(resourceRoot + '/')) { + return event.respondWith(processResourceRequest(event, requestUrl)); } - event.respondWith((async () => { - const resourcePath = requestUrl.pathname.replace(resourceRoot, ''); - - const existing = resolvedPaths.get(resourcePath); - if (existing) { - return existing.promise.then(r => r.clone()); - } - - const allClients = await clients.matchAll({ - includeUncontrolled: true - }); - - for (const client of allClients) { - const clientUrl = new URL(client.url); - if (clientUrl.pathname === '/') { - client.postMessage({ - channel: 'load-resource', - path: resourcePath - }); - - if (resolvedPaths.has(resourcePath)) { - // Someone else added it in the mean time - return resolvedPaths.get(resolvedPaths).promise; - } - - let resolve; - const promise = new Promise(r => resolve = r); - resolvedPaths.set(resourcePath, { resolve, promise }); - return promise.then(r => r.clone()); - } - } - })()); + // See if it's a localhost request + if (requestUrl.origin !== self.origin && requestUrl.host.match(/^localhost:(\d+)$/)) { + return event.respondWith(processLocalhostRequest(event, requestUrl)); + } }); self.addEventListener('install', (event) => { @@ -82,3 +170,105 @@ self.addEventListener('install', (event) => { self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()); // Become available to all pages }); + +async function processResourceRequest(event, requestUrl) { + const client = await self.clients.get(event.clientId); + if (!client) { + console.log('Could not find inner client for request'); + return notFound(); + } + + const webviewId = getWebviewIdForClient(client); + const resourcePath = requestUrl.pathname.replace(resourceRoot, ''); + + function resolveResourceEntry(entry) { + if (!entry) { + return notFound(); + } + return new Response(entry.body, { + status: 200, + headers: { 'Content-Type': entry.mime } + }); + } + + const parentClient = await getOuterIframeClient(webviewId); + if (!parentClient) { + console.log('Could not find parent client for request'); + return notFound(); + } + + // Check if we've already resolved this request + const existing = resourceRequestStore.get(webviewId, resourcePath); + if (existing) { + return existing.then(resolveResourceEntry); + } + + parentClient.postMessage({ + channel: 'load-resource', + path: resourcePath + }); + + return resourceRequestStore.create(webviewId, resourcePath) + .then(resolveResourceEntry); +} + +/** + * @param {*} event + * @param {URL} requestUrl + */ +async function processLocalhostRequest(event, requestUrl) { + const client = await self.clients.get(event.clientId); + if (!client) { + // This is expected when requesting resources on other localhost ports + // that are not spawned by vs code + return undefined; + } + const webviewId = getWebviewIdForClient(client); + const origin = requestUrl.origin; + + const resolveRedirect = redirectOrigin => { + if (!redirectOrigin) { + return fetch(event.request); + } + const location = event.request.url.replace(new RegExp(`^${requestUrl.origin}(/|$)`), `${redirectOrigin}$1`); + return new Response(null, { + status: 302, + headers: { + Location: location + } + }); + }; + + const parentClient = await getOuterIframeClient(webviewId); + if (!parentClient) { + console.log('Could not find parent client for request'); + return notFound(); + } + + // Check if we've already resolved this request + const existing = localhostRequestStore.get(webviewId, origin); + if (existing) { + return existing.then(resolveRedirect); + } + + parentClient.postMessage({ + channel: 'load-localhost', + origin: origin + }); + + return localhostRequestStore.create(webviewId, origin) + .then(resolveRedirect); +} + +function getWebviewIdForClient(client) { + const requesterClientUrl = new URL(client.url); + return requesterClientUrl.search.match(/\bid=([a-z0-9-]+)/i)[1]; +} + +async function getOuterIframeClient(webviewId) { + const allClients = await self.clients.matchAll({ includeUncontrolled: true }); + return allClients.find(client => { + const clientUrl = new URL(client.url); + return clientUrl.pathname === '/' && clientUrl.search.match(new RegExp('\\bid=' + webviewId)); + }); +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index 61af7e2c34b..32f91cd0ec9 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -17,8 +17,8 @@ import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } fro import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from 'vs/workbench/contrib/webview/common/webview'; -import { CopyWebviewEditorCommand, CutWebviewEditorCommand, HideWebViewEditorFindCommand, OpenWebviewDeveloperToolsAction, PasteWebviewEditorCommand, RedoWebviewEditorCommand, ReloadWebviewAction, SelectAllWebviewEditorCommand, ShowWebViewEditorFindWidgetCommand, UndoWebviewEditorCommand } from '../browser/webviewCommands'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/common/webview'; +import { CopyWebviewEditorCommand, CutWebviewEditorCommand, HideWebViewEditorFindCommand, PasteWebviewEditorCommand, RedoWebviewEditorCommand, ReloadWebviewAction, SelectAllWebviewEditorCommand, ShowWebViewEditorFindWidgetCommand, UndoWebviewEditorCommand } from '../browser/webviewCommands'; import { WebviewEditor } from '../browser/webviewEditor'; import { WebviewEditorInput } from '../browser/webviewEditorInput'; import { IWebviewEditorService, WebviewEditorService } from '../browser/webviewEditorService'; @@ -35,9 +35,6 @@ Registry.as(EditorInputExtensions.EditorInputFactor registerSingleton(IWebviewEditorService, WebviewEditorService, true); - -const webviewDeveloperCategory = localize('developer', "Developer"); - const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); export function registerWebViewCommands(editorId: string): void { @@ -127,11 +124,6 @@ export function registerWebViewCommands(editorId: string): void { registerWebViewCommands(WebviewEditor.ID); -actionRegistry.registerWorkbenchAction( - new SyncActionDescriptor(OpenWebviewDeveloperToolsAction, OpenWebviewDeveloperToolsAction.ID, OpenWebviewDeveloperToolsAction.LABEL), - 'Open Webview Developer Tools', - webviewDeveloperCategory); - actionRegistry.registerWorkbenchAction( new SyncActionDescriptor(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), 'Reload Webviews', diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index 91b4ad454b9..dbf38d80b79 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -98,30 +98,6 @@ export class RedoWebviewEditorCommand extends Command { } } -export class OpenWebviewDeveloperToolsAction extends Action { - static readonly ID = 'workbench.action.webview.openDeveloperTools'; - static readonly LABEL = nls.localize('openToolsLabel', "Open Webview Developer Tools"); - - public constructor( - id: string, - label: string - ) { - super(id, label); - } - - public run(): Promise { - const elements = document.querySelectorAll('webview.ready'); - for (let i = 0; i < elements.length; i++) { - try { - (elements.item(i) as Electron.WebviewTag).openDevTools(); - } catch (e) { - console.error(e); - } - } - return Promise.resolve(true); - } -} - export class ReloadWebviewAction extends Action { static readonly ID = 'workbench.action.webview.reloadWebviewAction'; static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 52936cb045c..0c57649c8b6 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -6,17 +6,17 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Disposable } from 'vs/base/common/lifecycle'; import { areWebviewInputOptionsEqual } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; import { addDisposableListener, addClass } from 'vs/base/browser/dom'; import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { loadLocalResource } from 'vs/workbench/contrib/webview/common/resourceLoader'; +import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; +import { ITunnelService } from 'vs/platform/remote/common/tunnel'; interface WebviewContent { readonly html: string; @@ -25,26 +25,37 @@ interface WebviewContent { } export class IFrameWebview extends Disposable implements Webview { - private element: HTMLIFrameElement; + private element?: HTMLIFrameElement; - private _ready: Promise; + private readonly _ready: Promise; private content: WebviewContent; private _focused = false; private readonly id: string; + private readonly _portMappingManager: WebviewPortMappingManager; + constructor( private _options: WebviewOptions, contentOptions: WebviewContentOptions, - @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IEnvironmentService environmentService: IEnvironmentService, + @ITunnelService tunnelService: ITunnelService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService, - @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); + if (typeof environmentService.webviewEndpoint !== 'string') { + throw new Error('To use iframe based webviews, you must configure `environmentService.webviewEndpoint`'); + } + + this._portMappingManager = this._register(new WebviewPortMappingManager( + this._options.extension ? this._options.extension.location : undefined, + () => this.content.options.portMappings || [], + tunnelService + )); + this.content = { html: '', options: contentOptions, @@ -54,9 +65,8 @@ export class IFrameWebview extends Disposable implements Webview { this.id = `webview-${Date.now()}`; this.element = document.createElement('iframe'); - this.element.sandbox.add('allow-scripts'); - this.element.sandbox.add('allow-same-origin'); - this.element.setAttribute('src', `${environmentService.webviewEndpoint}?id=${this.id}`); // TODO: get this from env service + this.element.sandbox.add('allow-scripts', 'allow-same-origin'); + this.element.setAttribute('src', `${environmentService.webviewEndpoint}?id=${this.id}`); this.element.style.border = 'none'; this.element.style.width = '100%'; this.element.style.height = '100%'; @@ -74,20 +84,13 @@ export class IFrameWebview extends Disposable implements Webview { return; case 'did-click-link': - let [uri] = e.data.data; + const uri = e.data.data; this._onDidClickLink.fire(URI.parse(uri)); return; - case 'did-set-content': - // this._webview.style.flex = ''; - // this._webview.style.width = '100%'; - // this._webview.style.height = '100%'; - // this.layout(); - return; - case 'did-scroll': - // if (event.args && typeof event.args[0] === 'number') { - // this._onDidScroll.fire({ scrollYPercentage: event.args[0] }); + // if (e.args && typeof e.args[0] === 'number') { + // this._onDidScroll.fire({ scrollYPercentage: e.args[0] }); // } return; @@ -111,9 +114,15 @@ export class IFrameWebview extends Disposable implements Webview { case 'load-resource': { - const path = e.data.data.path; - const uri = URI.file(path); - this.loadResource(uri); + const requestPath = e.data.data.path; + const uri = URI.file(decodeURIComponent(requestPath)); + this.loadResource(requestPath, uri); + return; + } + + case 'load-localhost': + { + this.localLocalhost(e.data.data.origin); return; } } @@ -122,7 +131,9 @@ export class IFrameWebview extends Disposable implements Webview { this._ready = new Promise(resolve => { const subscription = this._register(addDisposableListener(window, 'message', (e) => { if (e.data && e.data.target === this.id && e.data.channel === 'webview-ready') { - addClass(this.element, 'ready'); + if (this.element) { + addClass(this.element, 'ready'); + } subscription.dispose(); resolve(); } @@ -134,7 +145,9 @@ export class IFrameWebview extends Disposable implements Webview { } public mountTo(parent: HTMLElement) { - parent.appendChild(this.element); + if (this.element) { + parent.appendChild(this.element); + } } public set options(options: WebviewContentOptions) { @@ -152,19 +165,24 @@ export class IFrameWebview extends Disposable implements Webview { public set html(value: string) { this.content = { - html: value, + html: this.preprocessHtml(value), options: this.content.options, state: this.content.state, }; this.doUpdateContent(); } + private preprocessHtml(value: string): string { + return value.replace(/(["'])vscode-resource:([^\s'"]+?)(["'])/gi, (_, startQuote, path, endQuote) => + `${startQuote}${this.environmentService.webviewEndpoint}/vscode-resource${path}${endQuote}`); + } + public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { if (retainContextWhenHidden && html === this.content.html && areWebviewInputOptionsEqual(options, this.content.options)) { return; } this.content = { - html: html, + html: this.preprocessHtml(html), options: options, state: this.content.state, }; @@ -187,7 +205,6 @@ export class IFrameWebview extends Disposable implements Webview { } initialScrollProgress: number; - state: string | undefined; private readonly _onDidFocus = this._register(new Emitter()); public readonly onDidFocus = this._onDidFocus.event; @@ -204,19 +221,20 @@ export class IFrameWebview extends Disposable implements Webview { private readonly _onMessage = this._register(new Emitter()); public readonly onMessage = this._onMessage.event; - sendMessage(data: any): void { this._send('message', data); } - layout(): void { // noop } focus(): void { - this.element.focus(); + if (this.element) { + this.element.focus(); + } } + dispose(): void { if (this.element) { if (this.element.parentElement) { @@ -229,8 +247,9 @@ export class IFrameWebview extends Disposable implements Webview { } reload(): void { - throw new Error('Method not implemented.'); + this.doUpdateContent(); } + selectAll(): void { throw new Error('Method not implemented.'); } @@ -256,30 +275,42 @@ export class IFrameWebview extends Disposable implements Webview { throw new Error('Method not implemented.'); } - private _send(channel: string, data: any): void { - this._ready - .then(() => this.element.contentWindow!.postMessage({ - channel: channel, - args: data - }, '*')) - .catch(err => console.error(err)); + public set state(state: string | undefined) { + this.content = { + html: this.content.html, + options: this.content.options, + state, + }; } + private _send(channel: string, data: any): void { + this._ready + .then(() => { + if (!this.element) { + return; + } + this.element.contentWindow!.postMessage({ + channel: channel, + args: data + }, '*'); + }) + .catch(err => console.error(err)); + } private style(theme: ITheme): void { const { styles, activeTheme } = getWebviewThemeData(theme, this._configurationService); this._send('styles', { styles, activeTheme }); } - private async loadResource(uri: URI) { + private async loadResource(requestPath: string, uri: URI) { try { const result = await loadLocalResource(uri, this.fileService, this._options.extension ? this._options.extension.location : undefined, () => (this.content.options.localResourceRoots || [])); if (result.type === 'success') { - return this._send('loaded-resource', { + return this._send('did-load-resource', { status: 200, - path: uri.path, + path: requestPath, mime: result.mimeType, data: result.data.buffer }); @@ -288,10 +319,17 @@ export class IFrameWebview extends Disposable implements Webview { // noop } - return this._send('loaded-resource', { + return this._send('did-load-resource', { status: 404, path: uri.path }); } -} + private async localLocalhost(origin: string) { + const redirect = await this._portMappingManager.getRedirect(origin); + return this._send('did-load-localhost', { + origin, + location: redirect + }); + } +} diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts new file mode 100644 index 00000000000..964c0ac6da0 --- /dev/null +++ b/src/vs/workbench/contrib/webview/common/portMapping.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import * as modes from 'vs/editor/common/modes'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; + +export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined { + if (uri.scheme !== 'http' && uri.scheme !== 'https') { + return undefined; + } + const localhostMatch = /^(localhost|127\.0\.0\.1):(\d+)$/.exec(uri.authority); + if (!localhostMatch) { + return undefined; + } + return { + address: localhostMatch[1], + port: +localhostMatch[2], + }; +} + +export class WebviewPortMappingManager extends Disposable { + + private readonly _tunnels = new Map>(); + + constructor( + private readonly extensionLocation: URI | undefined, + private readonly mappings: () => ReadonlyArray, + private readonly tunnelService: ITunnelService + ) { + super(); + } + + public async getRedirect(url: string): Promise { + const uri = URI.parse(url); + const requestLocalHostInfo = extractLocalHostUriMetaDataForPortMapping(uri); + if (!requestLocalHostInfo) { + return undefined; + } + + for (const mapping of this.mappings()) { + if (mapping.webviewPort === requestLocalHostInfo.port) { + if (this.extensionLocation && this.extensionLocation.scheme === REMOTE_HOST_SCHEME) { + const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort); + if (tunnel) { + return uri.with({ + authority: `127.0.0.1:${tunnel.tunnelLocalPort}`, + }).toString(); + } + } + + if (mapping.webviewPort !== mapping.extensionHostPort) { + return uri.with({ + authority: `${requestLocalHostInfo.address}:${mapping.extensionHostPort}` + }).toString(); + } + } + } + + return undefined; + } + + dispose() { + super.dispose(); + + for (const tunnel of this._tunnels.values()) { + tunnel.then(tunnel => tunnel.dispose()); + } + this._tunnels.clear(); + } + + private getOrCreateTunnel(remotePort: number): Promise | undefined { + const existing = this._tunnels.get(remotePort); + if (existing) { + return existing; + } + const tunnel = this.tunnelService.openTunnel(remotePort); + if (tunnel) { + this._tunnels.set(remotePort, tunnel); + } + return tunnel; + } +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/common/webview.ts index 0117b141c77..ca427d11f57 100644 --- a/src/vs/workbench/contrib/webview/common/webview.ts +++ b/src/vs/workbench/contrib/webview/common/webview.ts @@ -10,6 +10,7 @@ import * as modes from 'vs/editor/common/modes'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import * as nls from 'vs/nls'; /** * Set when the find widget in a webview is visible. @@ -83,3 +84,5 @@ export interface Webview extends IDisposable { showFind(): void; hideFind(): void; } + +export const webviewDeveloperCategory = nls.localize('developer', "Developer"); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index 7b7b2c2dc76..cd912708989 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -3,8 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWebviewService } from 'vs/workbench/contrib/webview/common/webview'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { IWebviewService, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/common/webview'; import { WebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService'; +import { OpenWebviewDeveloperToolsAction } from 'vs/workbench/contrib/webview/electron-browser/webviewCommands'; registerSingleton(IWebviewService, WebviewService, true); + +const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); + +actionRegistry.registerWorkbenchAction( + new SyncActionDescriptor(OpenWebviewDeveloperToolsAction, OpenWebviewDeveloperToolsAction.ID, OpenWebviewDeveloperToolsAction.LABEL), + OpenWebviewDeveloperToolsAction.ALIAS, + webviewDeveloperCategory); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts new file mode 100644 index 00000000000..e239b767855 --- /dev/null +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; +import * as nls from 'vs/nls'; + +export class OpenWebviewDeveloperToolsAction extends Action { + static readonly ID = 'workbench.action.webview.openDeveloperTools'; + static readonly ALIAS = 'Open Webview Developer Tools'; + static readonly LABEL = nls.localize('openToolsLabel', "Open Webview Developer Tools"); + + public constructor(id: string, label: string) { + super(id, label); + } + + public run(): Promise { + const elements = document.querySelectorAll('webview.ready'); + for (let i = 0; i < elements.length; i++) { + try { + (elements.item(i) as Electron.WebviewTag).openDevTools(); + } catch (e) { + console.error(e); + } + } + return Promise.resolve(true); + } +} diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index b86e3120d00..67ca21ee160 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -13,19 +13,17 @@ import { endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; +import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; import { Webview, WebviewContentOptions, WebviewOptions, WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/webview'; import { registerFileProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols'; import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService'; import { WebviewFindWidget } from '../browser/webviewFindWidget'; -import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; export interface WebviewPortMapping { readonly port: number; @@ -141,88 +139,22 @@ class WebviewProtocolProvider extends Disposable { class WebviewPortMappingProvider extends Disposable { - private readonly _tunnels = new Map>(); + private readonly _manager: WebviewPortMappingManager; constructor( session: WebviewSession, extensionLocation: URI | undefined, mappings: () => ReadonlyArray, - private readonly tunnelService: ITunnelService, - extensionId: ExtensionIdentifier | undefined, - @ITelemetryService telemetryService: ITelemetryService + tunnelService: ITunnelService, ) { super(); + this._manager = this._register(new WebviewPortMappingManager(extensionLocation, mappings, tunnelService)); - let hasLogged = false; - - session.onBeforeRequest(async (details) => { - const uri = URI.parse(details.url); - if (uri.scheme !== 'http' && uri.scheme !== 'https') { - return undefined; - } - - const localhostMatch = /^localhost:(\d+)$/.exec(uri.authority); - if (localhostMatch) { - if (!hasLogged && extensionId) { - hasLogged = true; - - /* __GDPR__ - "webview.accessLocalhost" : { - "extension" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - telemetryService.publicLog('webview.accessLocalhost', { extension: extensionId.value }); - } - - const port = +localhostMatch[1]; - for (const mapping of mappings()) { - if (mapping.webviewPort === port) { - if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) { - const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort); - if (tunnel) { - return { - redirectURL: details.url.replace( - new RegExp(`^${uri.scheme}://localhost:${mapping.webviewPort}/`), - `${uri.scheme}://localhost:${tunnel.tunnelLocalPort}/`) - }; - } - } - - if (mapping.webviewPort !== mapping.extensionHostPort) { - return { - redirectURL: details.url.replace( - new RegExp(`^${uri.scheme}://localhost:${mapping.webviewPort}/`), - `${uri.scheme}://localhost:${mapping.extensionHostPort}/`) - }; - } - } - } - } - - return undefined; + session.onBeforeRequest(async details => { + const redirect = await this._manager.getRedirect(details.url); + return redirect ? { redirectURL: redirect } : undefined; }); } - - dispose() { - super.dispose(); - - for (const tunnel of this._tunnels.values()) { - tunnel.then(tunnel => tunnel.dispose()); - } - this._tunnels.clear(); - } - - private getOrCreateTunnel(remotePort: number): Promise | undefined { - const existing = this._tunnels.get(remotePort); - if (existing) { - return existing; - } - const tunnel = this.tunnelService.openTunnel(remotePort); - if (tunnel) { - this._tunnels.set(remotePort, tunnel); - } - return tunnel; - } } class SvgBlocker extends Disposable { @@ -370,10 +302,8 @@ export class WebviewElement extends Disposable implements Webview { contentOptions: WebviewContentOptions, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @ITunnelService tunnelService: ITunnelService, - @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); @@ -420,8 +350,6 @@ export class WebviewElement extends Disposable implements Webview { _options.extension ? _options.extension.location : undefined, () => (this.content.options.portMappings || []), tunnelService, - _options.extension ? _options.extension.id : undefined, - telemetryService )); if (!this._options.allowSvgs) { @@ -463,6 +391,18 @@ export class WebviewElement extends Disposable implements Webview { this._onDidClickLink.fire(URI.parse(uri)); return; + case 'synthetic-mouse-event': + { + const rawEvent = event.args[0]; + const bounds = this._webview.getBoundingClientRect(); + window.dispatchEvent(new MouseEvent(rawEvent.type, { + ...rawEvent, + clientX: rawEvent.clientX + bounds.left, + clientY: rawEvent.clientY + bounds.top, + })); + return; + } + case 'did-set-content': this._webview.style.flex = ''; this._webview.style.width = '100%'; diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts index 2c31578abdc..67edffb9c5f 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { language, locale } from 'vs/base/common/platform'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 57ec2510560..071c367c648 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -151,7 +151,7 @@ const extensionPacks: ExtensionSuggestion[] = [ // { name: localize('welcomePage.go', "Go"), id: 'lukehoban.go' }, { name: localize('welcomePage.php', "PHP"), id: 'felixfbecker.php-pack' }, { name: localize('welcomePage.azure', "Azure"), title: localize('welcomePage.showAzureExtensions', "Show Azure extensions"), id: 'workbench.extensions.action.showAzureExtensions', isCommand: true }, - { name: localize('welcomePage.docker', "Docker"), id: 'peterjausovec.vscode-docker' }, + { name: localize('welcomePage.docker', "Docker"), id: 'ms-azuretools.vscode-docker' }, ]; const keymapExtensions: ExtensionSuggestion[] = [ diff --git a/src/vs/workbench/electron-browser/actions/developerActions.ts b/src/vs/workbench/electron-browser/actions/developerActions.ts index 811c9ecac13..4da3b429e48 100644 --- a/src/vs/workbench/electron-browser/actions/developerActions.ts +++ b/src/vs/workbench/electron-browser/actions/developerActions.ts @@ -6,16 +6,6 @@ import { Action } from 'vs/base/common/actions'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { domEvent } from 'vs/base/browser/event'; -import { Event } from 'vs/base/common/event'; -import { IDisposable, toDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $ } from 'vs/base/browser/dom'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { timeout } from 'vs/base/common/async'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class ToggleDevToolsAction extends Action { @@ -44,176 +34,3 @@ export class ToggleSharedProcessAction extends Action { return this.windowsService.toggleSharedProcess(); } } - -export class InspectContextKeysAction extends Action { - - static readonly ID = 'workbench.action.inspectContextKeys'; - static LABEL = nls.localize('inspect context keys', "Inspect Context Keys"); - - constructor( - id: string, - label: string, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IWindowService private readonly windowService: IWindowService, - ) { - super(id, label); - } - - run(): Promise { - const disposables = new DisposableStore(); - - const stylesheet = createStyleSheet(); - disposables.add(toDisposable(() => { - if (stylesheet.parentNode) { - stylesheet.parentNode.removeChild(stylesheet); - } - })); - createCSSRule('*', 'cursor: crosshair !important;', stylesheet); - - const hoverFeedback = document.createElement('div'); - document.body.appendChild(hoverFeedback); - disposables.add(toDisposable(() => document.body.removeChild(hoverFeedback))); - - hoverFeedback.style.position = 'absolute'; - hoverFeedback.style.pointerEvents = 'none'; - hoverFeedback.style.backgroundColor = 'rgba(255, 0, 0, 0.5)'; - hoverFeedback.style.zIndex = '1000'; - - const onMouseMove = domEvent(document.body, 'mousemove', true); - disposables.add(onMouseMove(e => { - const target = e.target as HTMLElement; - const position = getDomNodePagePosition(target); - - hoverFeedback.style.top = `${position.top}px`; - hoverFeedback.style.left = `${position.left}px`; - hoverFeedback.style.width = `${position.width}px`; - hoverFeedback.style.height = `${position.height}px`; - })); - - const onMouseDown = Event.once(domEvent(document.body, 'mousedown', true)); - onMouseDown(e => { e.preventDefault(); e.stopPropagation(); }, null, disposables); - - const onMouseUp = Event.once(domEvent(document.body, 'mouseup', true)); - onMouseUp(e => { - e.preventDefault(); - e.stopPropagation(); - - const context = this.contextKeyService.getContext(e.target as HTMLElement) as Context; - console.log(context.collectAllValues()); - this.windowService.openDevTools(); - - dispose(disposables); - }, null, disposables); - - return Promise.resolve(); - } -} - -export class ToggleScreencastModeAction extends Action { - - static readonly ID = 'workbench.action.toggleScreencastMode'; - static LABEL = nls.localize('toggle screencast mode', "Toggle Screencast Mode"); - - static disposable: IDisposable | undefined; - - constructor( - id: string, - label: string, - @IKeybindingService private readonly keybindingService: IKeybindingService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService - ) { - super(id, label); - } - - async run(): Promise { - if (ToggleScreencastModeAction.disposable) { - ToggleScreencastModeAction.disposable.dispose(); - ToggleScreencastModeAction.disposable = undefined; - return; - } - - const container = this.layoutService.getWorkbenchElement(); - - const mouseMarker = append(container, $('div')); - mouseMarker.style.position = 'absolute'; - mouseMarker.style.border = '2px solid red'; - mouseMarker.style.borderRadius = '20px'; - mouseMarker.style.width = '20px'; - mouseMarker.style.height = '20px'; - mouseMarker.style.top = '0'; - mouseMarker.style.left = '0'; - mouseMarker.style.zIndex = '100000'; - mouseMarker.style.content = ' '; - mouseMarker.style.pointerEvents = 'none'; - mouseMarker.style.display = 'none'; - - const onMouseDown = domEvent(container, 'mousedown', true); - const onMouseUp = domEvent(container, 'mouseup', true); - const onMouseMove = domEvent(container, 'mousemove', true); - - const mouseListener = onMouseDown(e => { - mouseMarker.style.top = `${e.clientY - 10}px`; - mouseMarker.style.left = `${e.clientX - 10}px`; - mouseMarker.style.display = 'block'; - - const mouseMoveListener = onMouseMove(e => { - mouseMarker.style.top = `${e.clientY - 10}px`; - mouseMarker.style.left = `${e.clientX - 10}px`; - }); - - Event.once(onMouseUp)(() => { - mouseMarker.style.display = 'none'; - mouseMoveListener.dispose(); - }); - }); - - const keyboardMarker = append(container, $('div')); - keyboardMarker.style.position = 'absolute'; - keyboardMarker.style.backgroundColor = 'rgba(0, 0, 0 ,0.5)'; - keyboardMarker.style.width = '100%'; - keyboardMarker.style.height = '100px'; - keyboardMarker.style.bottom = '20%'; - keyboardMarker.style.left = '0'; - keyboardMarker.style.zIndex = '100000'; - keyboardMarker.style.pointerEvents = 'none'; - keyboardMarker.style.color = 'white'; - keyboardMarker.style.lineHeight = '100px'; - keyboardMarker.style.textAlign = 'center'; - keyboardMarker.style.fontSize = '56px'; - keyboardMarker.style.display = 'none'; - - const onKeyDown = domEvent(container, 'keydown', true); - let keyboardTimeout: IDisposable = Disposable.None; - - const keyboardListener = onKeyDown(e => { - keyboardTimeout.dispose(); - - const event = new StandardKeyboardEvent(e); - const keybinding = this.keybindingService.resolveKeyboardEvent(event); - const label = keybinding.getLabel(); - - if (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && this.keybindingService.mightProducePrintableCharacter(event) && label) { - keyboardMarker.textContent += ' ' + label; - } else { - keyboardMarker.textContent = label; - } - - keyboardMarker.style.display = 'block'; - - const promise = timeout(800); - keyboardTimeout = toDisposable(() => promise.cancel()); - - promise.then(() => { - keyboardMarker.textContent = ''; - keyboardMarker.style.display = 'none'; - }); - }); - - ToggleScreencastModeAction.disposable = toDisposable(() => { - mouseListener.dispose(); - keyboardListener.dispose(); - mouseMarker.remove(); - keyboardMarker.remove(); - }); - } -} diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index ce3d1279119..58b52edeb51 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -61,20 +61,6 @@ export class NewWindowAction extends Action { } } -export class ToggleFullScreenAction extends Action { - - static readonly ID = 'workbench.action.toggleFullScreen'; - static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); - - constructor(id: string, label: string, @IWindowService private readonly windowService: IWindowService) { - super(id, label); - } - - run(): Promise { - return this.windowService.toggleFullScreen(); - } -} - export abstract class BaseZoomAction extends Action { private static readonly SETTING_KEY = 'window.zoomLevel'; @@ -164,26 +150,6 @@ export class ZoomResetAction extends BaseZoomAction { } } -export class ReloadWindowAction extends Action { - - static readonly ID = 'workbench.action.reloadWindow'; - static LABEL = nls.localize('reloadWindow', "Reload Window"); - - constructor( - id: string, - label: string, - @IWindowService private readonly windowService: IWindowService - ) { - super(id, label); - } - - async run(): Promise { - await this.windowService.reloadWindow(); - - return true; - } -} - export class ReloadWindowWithExtensionsDisabledAction extends Action { static readonly ID = 'workbench.action.reloadWindowWithExtensionsDisabled'; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 140f2039603..9d2b119c091 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -12,16 +12,16 @@ import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/action import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, OpenNewsletterSignupUrlAction } from 'vs/workbench/electron-browser/actions/helpActions'; -import { ToggleSharedProcessAction, InspectContextKeysAction, ToggleScreencastModeAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; -import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, OpenRecentAction, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ReloadWindowAction, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; -import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction, CloseWorkspaceAction, OpenLocalFileAction, OpenLocalFolderAction, OpenLocalFileFolderAction, SaveLocalFileAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { ToggleSharedProcessAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; +import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, OpenRecentAction, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; +import { AddRootFolderAction, GlobalRemoveRootFolderAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; -import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext, RemoteFileDialogContext, IsFullscreenContext } from 'vs/workbench/browser/contextkeys'; +import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { LogStorageAction } from 'vs/platform/storage/node/storageService'; @@ -35,17 +35,6 @@ import product from 'vs/platform/product/node/product'; (function registerFileActions(): void { const fileCategory = nls.localize('file', "File"); - if (isMacintosh) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFileFolderAction, OpenLocalFileFolderAction.ID, OpenLocalFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, RemoteFileDialogContext), 'File: Open Local...', fileCategory); - } else { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFileAction, OpenLocalFileAction.ID, OpenLocalFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, RemoteFileDialogContext), 'File: Open Local File...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFolderAction, OpenLocalFolderAction.ID, OpenLocalFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }, RemoteFileDialogContext), 'File: Open Local Folder...', fileCategory); - } - - registry.registerWorkbenchAction(new SyncActionDescriptor(SaveLocalFileAction, SaveLocalFileAction.ID, SaveLocalFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S }, RemoteFileDialogContext), 'File: Save Local File...', fileCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory); @@ -80,7 +69,6 @@ import product from 'vs/platform/product/node/product'; registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL, KeyMod.CtrlCmd | KeyCode.NUMPAD_ADD] }), 'View: Zoom In', viewCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS, KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT], linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT] } }), 'View: Zoom Out', viewCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomResetAction, ZoomResetAction.ID, ZoomResetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.NUMPAD_0 }), 'View: Reset Zoom', viewCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); })(); // Actions: Window @@ -120,7 +108,6 @@ import product from 'vs/platform/product/node/product'; registry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, SupportsWorkspacesContext); registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, SupportsWorkspacesContext); registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, SupportsWorkspacesContext); registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); @@ -162,20 +149,10 @@ import product from 'vs/platform/product/node/product'; (function registerDeveloperActions(): void { const developerCategory = nls.localize('developer', "Developer"); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Developer: Reload Window With Extensions Disabled', developerCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); - KeybindingsRegistry.registerKeybindingRule({ - id: ReloadWindowAction.ID, - weight: KeybindingWeight.WorkbenchContrib + 50, - when: IsDevelopmentContext, - primary: KeyMod.CtrlCmd | KeyCode.KEY_R - }); - KeybindingsRegistry.registerKeybindingRule({ id: ToggleDevToolsAction.ID, weight: KeybindingWeight.WorkbenchContrib + 50, @@ -228,45 +205,6 @@ import product from 'vs/platform/product/node/product'; order: 2 }); - if (isMacintosh) { - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFileFolderAction.ID, - title: nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...") - }, - order: 1 - }); - } else { - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFileAction.ID, - title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFolderAction.ID, - title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") - }, - order: 2 - }); - } - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenWorkspaceAction.ID, - title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") - }, - order: 3, - when: SupportsWorkspacesContext - }); - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), submenu: MenuId.MenubarRecentMenu, @@ -344,17 +282,6 @@ import product from 'vs/platform/product/node/product'; when: IsMacContext.toNegated() }); - // Appereance menu - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '1_toggle_view', - command: { - id: ToggleFullScreenAction.ID, - title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "&&Full Screen"), - toggled: IsFullscreenContext - }, - order: 1 - }); - // Zoom MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { @@ -538,18 +465,6 @@ import product from 'vs/platform/product/node/product'; nls.localize('openFilesInNewWindowMac', "Controls whether files should open in a new window. \nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") : nls.localize('openFilesInNewWindow', "Controls whether files should open in a new window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") }, - 'window.openFoldersInNewWindow': { - 'type': 'string', - 'enum': ['on', 'off', 'default'], - 'enumDescriptions': [ - nls.localize('window.openFoldersInNewWindow.on', "Folders will open in a new window."), - nls.localize('window.openFoldersInNewWindow.off', "Folders will replace the last active window."), - nls.localize('window.openFoldersInNewWindow.default', "Folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu).") - ], - 'default': 'default', - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('openFoldersInNewWindow', "Controls whether folders should open in a new window or replace the last active window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") - }, 'window.openWithoutArgumentsInNewWindow': { 'type': 'string', 'enum': ['on', 'off'], @@ -603,34 +518,6 @@ import product from 'vs/platform/product/node/product'; 'default': false, 'description': nls.localize('closeWhenEmpty', "Controls whether closing the last editor should also close the window. This setting only applies for windows that do not show folders.") }, - 'window.menuBarVisibility': { - 'type': 'string', - 'enum': ['default', 'visible', 'toggle', 'hidden'], - 'enumDescriptions': [ - nls.localize('window.menuBarVisibility.default', "Menu is only hidden in full screen mode."), - nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), - nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), - nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden.") - ], - 'default': 'default', - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('menuBarVisibility', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it. By default, the menu bar will be visible, unless the window is full screen."), - 'included': isWindows || isLinux - }, - 'window.enableMenuBarMnemonics': { - 'type': 'boolean', - 'default': true, - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('enableMenuBarMnemonics', "If enabled, the main menus can be opened via Alt-key shortcuts. Disabling mnemonics allows to bind these Alt-key shortcuts to editor commands instead."), - 'included': isWindows || isLinux - }, - 'window.disableCustomMenuBarAltFocus': { - 'type': 'boolean', - 'default': false, - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('disableCustomMenuBarAltFocus', "If enabled, disables the ability to focus the menu bar with the Alt-key when not set to toggle."), - 'included': isWindows || isLinux - }, 'window.autoDetectHighContrast': { 'type': 'boolean', 'default': true, @@ -663,7 +550,7 @@ import product from 'vs/platform/product/node/product'; 'default': true, 'description': nls.localize('window.nativeFullScreen', "Controls if native full-screen should be used on macOS. Disable this option to prevent macOS from creating a new space when going full-screen."), 'scope': ConfigurationScope.APPLICATION, - 'included': isMacintosh + 'included': false /* isMacintosh */ }, 'window.clickThroughInactive': { 'type': 'boolean', diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 921b9642456..87259d771c9 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -25,7 +25,7 @@ import { webFrame } from 'electron'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log'; import { StorageService } from 'vs/platform/storage/node/storageService'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc'; +import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/common/extpath'; import { basename } from 'vs/base/common/path'; @@ -47,7 +47,6 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; import { DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationExportHelper'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; -import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -306,10 +305,7 @@ class CodeRendererMain extends Disposable { } private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { - const configurationFileService = new ConfigurationFileService(); - configurationFileService.fileService = fileService; - - const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); + const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache(environmentService) }, fileService, remoteAgentService); try { await workspaceService.initialize(payload); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 02331934375..a2f76032809 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -12,7 +12,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, IConfigurationFileService, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES, ConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -20,14 +20,14 @@ import { ConfigurationScope } from 'vs/platform/configuration/common/configurati import { extname, join } from 'vs/base/common/path'; import { equals } from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; -import { IConfigurationModel, compare } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { hash } from 'vs/base/common/hash'; export class RemoteUserConfiguration extends Disposable { private readonly _cachedConfiguration: CachedUserConfiguration; - private readonly _configurationFileService: IConfigurationFileService; + private readonly _configurationFileService: ConfigurationFileService; private _userConfiguration: UserConfiguration | CachedUserConfiguration; private _userConfigurationInitializationPromise: Promise | null = null; @@ -37,7 +37,7 @@ export class RemoteUserConfiguration extends Disposable { constructor( remoteAuthority: string, configurationCache: IConfigurationCache, - configurationFileService: IConfigurationFileService, + configurationFileService: ConfigurationFileService, remoteAgentService: IRemoteAgentService ) { super(); @@ -103,7 +103,7 @@ export class UserConfiguration extends Disposable { constructor( private readonly configurationResource: URI, private readonly scopes: ConfigurationScope[] | undefined, - private readonly configurationFileService: IConfigurationFileService + private readonly configurationFileService: ConfigurationFileService ) { super(); @@ -138,11 +138,7 @@ export class UserConfiguration extends Disposable { async initialize(): Promise { const exists = await this.configurationFileService.exists(this.configurationResource); this.onResourceExists(exists); - const configuraitonModel = await this.reload(); - if (!this.configurationFileService.isWatching) { - this.configurationFileService.whenWatchingStarted.then(() => this.onWatchStarted(configuraitonModel)); - } - return configuraitonModel; + return this.reload(); } async reload(): Promise { @@ -160,14 +156,6 @@ export class UserConfiguration extends Disposable { return this.parser.configurationModel; } - private async onWatchStarted(currentModel: ConfigurationModel): Promise { - const configuraitonModel = await this.reload(); - const { added, removed, updated } = compare(currentModel, configuraitonModel); - if (added.length || removed.length || updated.length) { - this._onDidChangeConfiguration.fire(configuraitonModel); - } - } - private async handleFileEvents(event: FileChangesEvent): Promise { const events = event.changes; @@ -252,7 +240,7 @@ class CachedUserConfiguration extends Disposable { export class WorkspaceConfiguration extends Disposable { - private readonly _configurationFileService: IConfigurationFileService; + private readonly _configurationFileService: ConfigurationFileService; private readonly _cachedConfiguration: CachedWorkspaceConfiguration; private _workspaceConfiguration: IWorkspaceConfiguration; private _workspaceConfigurationChangeDisposable: IDisposable = Disposable.None; @@ -266,7 +254,7 @@ export class WorkspaceConfiguration extends Disposable { constructor( configurationCache: IConfigurationCache, - configurationFileService: IConfigurationFileService + configurationFileService: ConfigurationFileService ) { super(); this._configurationFileService = configurationFileService; @@ -369,7 +357,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWork protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - constructor(private configurationFileService: IConfigurationFileService) { + constructor(private configurationFileService: ConfigurationFileService) { super(); this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(''); @@ -378,10 +366,6 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWork this._register(configurationFileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); this.workspaceConfigWatcher = this._register(this.watchWorkspaceConfigurationFile()); - - if (!this.configurationFileService.isWatching) { - this.configurationFileService.whenWatchingStarted.then(() => this.onWatchStarted()); - } } get workspaceIdentifier(): IWorkspaceIdentifier | null { @@ -426,18 +410,6 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWork return this.getWorkspaceSettings(); } - private async onWatchStarted(): Promise { - if (this.workspaceIdentifier) { - const currentModel = this.getConfigurationModel(); - await this.load(this.workspaceIdentifier); - const newModel = this.getConfigurationModel(); - const { added, removed, updated } = compare(currentModel, newModel); - if (added.length || removed.length || updated.length) { - this._onDidChange.fire(); - } - } - } - private consolidate(): void { this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel); } @@ -546,7 +518,7 @@ class FileServiceBasedFolderConfiguration extends Disposable implements IFolderC protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - constructor(protected readonly configurationFolder: URI, workbenchState: WorkbenchState, private configurationFileService: IConfigurationFileService) { + constructor(protected readonly configurationFolder: URI, workbenchState: WorkbenchState, private configurationFileService: ConfigurationFileService) { super(); this.configurationNames = [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY]; @@ -557,9 +529,6 @@ class FileServiceBasedFolderConfiguration extends Disposable implements IFolderC this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); this._register(configurationFileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); - if (!this.configurationFileService.isWatching) { - this.configurationFileService.whenWatchingStarted.then(() => this.onWatchStarted()); - } } async loadConfiguration(): Promise { @@ -611,15 +580,6 @@ class FileServiceBasedFolderConfiguration extends Disposable implements IFolderC this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations); } - private async onWatchStarted(): Promise { - const currentModel = this._cache; - const newModel = await this.loadConfiguration(); - const { added, removed, updated } = compare(currentModel, newModel); - if (added.length || removed.length || updated.length) { - this._onDidChange.fire(); - } - } - private handleWorkspaceFileEvents(event: FileChangesEvent): void { const events = event.changes; let affectedByChanges = false; @@ -725,7 +685,7 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat readonly workspaceFolder: IWorkspaceFolder, configFolderRelativePath: string, private readonly workbenchState: WorkbenchState, - configurationFileService: IConfigurationFileService, + configurationFileService: ConfigurationFileService, configurationCache: IConfigurationCache ) { super(); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 618fb0bc326..0152a86f84a 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -15,7 +15,7 @@ import { isLinux } from 'vs/base/common/platform'; import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, IConfigurationFileService, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, ConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; @@ -28,6 +28,7 @@ import { localize } from 'vs/nls'; import { isEqual, dirname } from 'vs/base/common/resources'; import { mark } from 'vs/base/common/performance'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IFileService } from 'vs/platform/files/common/files'; export class WorkspaceService extends Disposable implements IConfigurationService, IWorkspaceContextService { @@ -37,14 +38,16 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private completeWorkspaceBarrier: Barrier; private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; + private initialized: boolean = false; private defaultConfiguration: DefaultConfigurationModel; - private localUserConfiguration: UserConfiguration | null = null; + private localUserConfiguration: UserConfiguration; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; private cachedFolderConfigs: ResourceMap; - private workspaceEditingQueue: Queue; + private readonly configurationFileService: ConfigurationFileService; + protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; @@ -64,8 +67,8 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private cyclicDependency = new Promise(resolve => this.cyclicDependencyReady = resolve); constructor( - { userSettingsResource, remoteAuthority, configurationCache }: { userSettingsResource?: URI, remoteAuthority?: string, configurationCache: IConfigurationCache }, - private readonly configurationFileService: IConfigurationFileService, + { userSettingsResource, remoteAuthority, configurationCache }: { userSettingsResource: URI, remoteAuthority?: string, configurationCache: IConfigurationCache }, + fileService: IFileService, remoteAgentService: IRemoteAgentService, ) { super(); @@ -73,12 +76,13 @@ export class WorkspaceService extends Disposable implements IConfigurationServic this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = new DefaultConfigurationModel(); this.configurationCache = configurationCache; - if (userSettingsResource) { - this.localUserConfiguration = this._register(new UserConfiguration(userSettingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, configurationFileService)); - this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); - } + this.configurationFileService = new ConfigurationFileService(fileService); + this._configuration = new Configuration(this.defaultConfiguration, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); + this.cachedFolderConfigs = new ResourceMap(); + this.localUserConfiguration = this._register(new UserConfiguration(userSettingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, this.configurationFileService)); + this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); if (remoteAuthority) { - this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, configurationFileService, remoteAgentService)); + this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, this.configurationFileService, remoteAgentService)); this._register(this.remoteUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onRemoteUserConfigurationChanged(userConfiguration))); } this.workspaceConfiguration = this._register(new WorkspaceConfiguration(configurationCache, this.configurationFileService)); @@ -420,7 +424,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } private initializeUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { - return Promise.all([this.localUserConfiguration ? this.localUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel()), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]) + return Promise.all([this.localUserConfiguration.initialize(), this.remoteUserConfiguration ? this.remoteUserConfiguration.initialize() : Promise.resolve(new ConfigurationModel())]) .then(([local, remote]) => ({ local, remote })); } @@ -429,7 +433,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } private reloadLocalUserConfiguration(key?: string): Promise { - return this.localUserConfiguration ? this.localUserConfiguration.reload() : Promise.resolve(new ConfigurationModel()); + return this.localUserConfiguration.reload(); } private reloadRemoeUserConfiguration(key?: string): Promise { @@ -466,11 +470,12 @@ export class WorkspaceService extends Disposable implements IConfigurationServic const currentConfiguration = this._configuration; this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); - if (currentConfiguration) { + if (this.initialized) { const changedKeys = this._configuration.compare(currentConfiguration); this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE); } else { this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); + this.initialized = true; } }); } @@ -489,7 +494,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private onDefaultConfigurationChanged(keys: string[]): void { this.defaultConfiguration = new DefaultConfigurationModel(); this.registerConfigurationSchemas(); - if (this.workspace && this._configuration) { + if (this.workspace) { this._configuration.updateDefaultConfiguration(this.defaultConfiguration); if (this.remoteUserConfiguration) { this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reprocess()); @@ -545,14 +550,12 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } private onRemoteUserConfigurationChanged(userConfiguration: ConfigurationModel): void { - if (this._configuration) { - const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration); - this.triggerConfigurationChange(keys, ConfigurationTarget.USER); - } + const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration); + this.triggerConfigurationChange(keys, ConfigurationTarget.USER); } private onWorkspaceConfigurationChanged(): Promise { - if (this.workspace && this.workspace.configuration && this._configuration) { + if (this.workspace && this.workspace.configuration) { const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration()); let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration); const changes = this.compareFolders(this.workspace.folders, configuredFolders); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index a1185aa3001..2910b6feccc 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -5,10 +5,10 @@ import { URI } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; -import { FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +export const USER_CONFIGURATION_KEY = 'settings.json'; export const FOLDER_CONFIG_FOLDER_NAME = '.vscode'; export const FOLDER_SETTINGS_NAME = 'settings'; export const FOLDER_SETTINGS_PATH = `${FOLDER_CONFIG_FOLDER_NAME}/${FOLDER_SETTINGS_NAME}.json`; @@ -42,24 +42,11 @@ export interface IConfigurationCache { } -export interface IConfigurationFileService { - fileService: IFileService | null; - readonly onFileChanges: Event; - readonly isWatching: boolean; - readonly whenWatchingStarted: Promise; - whenProviderRegistered(scheme: string): Promise; - watch(resource: URI): IDisposable; - exists(resource: URI): Promise; - readFile(resource: URI): Promise; -} +export class ConfigurationFileService { -export class ConfigurationFileService implements IConfigurationFileService { - - constructor(public fileService: IFileService) { } + constructor(private readonly fileService: IFileService) { } get onFileChanges() { return this.fileService.onFileChanges; } - readonly whenWatchingStarted: Promise = Promise.resolve(); - readonly isWatching: boolean = true; whenProviderRegistered(scheme: string): Promise { if (this.fileService.canHandleResource(URI.from({ scheme }))) { diff --git a/src/vs/workbench/services/configuration/node/configurationFileService.ts b/src/vs/workbench/services/configuration/node/configurationFileService.ts deleted file mode 100644 index 18690c44040..00000000000 --- a/src/vs/workbench/services/configuration/node/configurationFileService.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as pfs from 'vs/base/node/pfs'; -import { IConfigurationFileService, ConfigurationFileService as FileServiceBasedConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; -import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; -import { FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; - -export class ConfigurationFileService extends Disposable implements IConfigurationFileService { - - private _fileServiceBasedConfigurationFileService: FileServiceBasedConfigurationFileService | null = null; - private _fileServiceBasedConfigurationFileServiceCallback: (fileServiceBasedConfigurationFileService: FileServiceBasedConfigurationFileService) => void; - private _whenFileServiceBasedConfigurationFileServiceAvailable: Promise = new Promise((c) => this._fileServiceBasedConfigurationFileServiceCallback = c); - private _watchResources: { resource: URI, disposable: { disposable: IDisposable | null } }[] = []; - readonly whenWatchingStarted: Promise = this._whenFileServiceBasedConfigurationFileServiceAvailable.then(() => undefined); - - private readonly _onFileChanges: Emitter = this._register(new Emitter()); - readonly onFileChanges: Event = this._onFileChanges.event; - - get isWatching(): boolean { - return this._fileServiceBasedConfigurationFileService ? this._fileServiceBasedConfigurationFileService.isWatching : false; - } - - watch(resource: URI): IDisposable { - if (this._fileServiceBasedConfigurationFileService) { - return this._fileServiceBasedConfigurationFileService.watch(resource); - } - const disposable: { disposable: IDisposable | null } = { disposable: null }; - this._watchResources.push({ resource, disposable }); - return toDisposable(() => { - if (disposable.disposable) { - disposable.disposable.dispose(); - } - }); - } - - whenProviderRegistered(scheme: string): Promise { - if (scheme === Schemas.file) { - return Promise.resolve(); - } - return this._whenFileServiceBasedConfigurationFileServiceAvailable - .then(fileServiceBasedConfigurationFileService => fileServiceBasedConfigurationFileService.whenProviderRegistered(scheme)); - } - - exists(resource: URI): Promise { - return this._fileServiceBasedConfigurationFileService ? this._fileServiceBasedConfigurationFileService.exists(resource) : pfs.exists(resource.fsPath); - } - - async readFile(resource: URI): Promise { - if (this._fileServiceBasedConfigurationFileService) { - return this._fileServiceBasedConfigurationFileService.readFile(resource); - } else { - const contents = await pfs.readFile(resource.fsPath); - return contents.toString(); - } - } - - private _fileService: IFileService | null; - get fileService(): IFileService | null { - return this._fileService; - } - - set fileService(fileService: IFileService | null) { - if (fileService && !this._fileServiceBasedConfigurationFileService) { - this._fileServiceBasedConfigurationFileService = new FileServiceBasedConfigurationFileService(fileService); - this._fileService = fileService; - this._register(this._fileServiceBasedConfigurationFileService.onFileChanges(e => this._onFileChanges.fire(e))); - for (const { resource, disposable } of this._watchResources) { - disposable.disposable = this._fileServiceBasedConfigurationFileService.watch(resource); - } - this._fileServiceBasedConfigurationFileServiceCallback(this._fileServiceBasedConfigurationFileService); - } - } -} diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 6634fcb6e04..64b5b994371 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -39,7 +39,6 @@ import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; -import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -104,14 +103,14 @@ suite('ConfigurationEditingService', () => { const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), remoteAgentService); + const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => { instantiationService.stub(IConfigurationService, workspaceService); - const fileService = new FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(ICommandService, CommandService); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 3fd5bf00d75..cbd400263ee 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -41,7 +41,6 @@ import { FileService } from 'vs/workbench/services/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; -import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -104,7 +103,7 @@ suite('WorkspaceContextService - Folder', () => { workspaceResource = folderDir; const globalSettingsFile = path.join(parentDir, 'settings.json'); const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); - workspaceContextService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), new RemoteAgentService({}, environmentService, new RemoteAuthorityResolverService(), new SignService())); + workspaceContextService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, new FileService(new NullLogService()), new RemoteAgentService({}, environmentService, new RemoteAuthorityResolverService(), new SignService())); return (workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir))); }); }); @@ -166,7 +165,9 @@ suite('WorkspaceContextService - Workspace', () => { const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json')); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), remoteAgentService); + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -224,8 +225,7 @@ suite('WorkspaceContextService - Workspace Editing', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - const configurationFileService = new ConfigurationFileService(); - configurationFileService.fileService = fileService; + const configurationFileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -485,8 +485,7 @@ suite('WorkspaceService - Initialization', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - const configurationFileService = new ConfigurationFileService(); - configurationFileService.fileService = fileService; + const configurationFileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -749,8 +748,7 @@ suite('WorkspaceConfigurationService - Folder', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - const configurationFileService = new ConfigurationFileService(); - configurationFileService.fileService = fileService; + const configurationFileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -1077,8 +1075,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - const configurationFileService = new ConfigurationFileService(); - configurationFileService.fileService = fileService; + const configurationFileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -1479,8 +1476,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - const configurationFileService = new ConfigurationFileService(); - configurationFileService.fileService = fileService; + const configurationFileService = fileService; const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() }; testObject = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache, remoteAuthority }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, testObject); diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index c9a8760aa96..bcf4b31cef9 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -19,8 +19,9 @@ import { AbstractVariableResolverService } from 'vs/workbench/services/configura import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IQuickInputService, IInputOptions, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; -import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { ConfiguredInput, IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IProcessEnvironment } from 'vs/base/common/platform'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export abstract class BaseConfigurationResolverService extends AbstractVariableResolverService { @@ -304,4 +305,6 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi ) { super(environmentService.configuration.userEnv, editorService, environmentService, configurationService, commandService, workspaceContextService, quickInputService); } -} \ No newline at end of file +} + +registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index fefbf28bc6e..fe43bbd5c3b 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -208,7 +208,7 @@ export class FileDialogService implements IFileDialogService { private toNativeSaveDialogOptions(options: ISaveDialogOptions): Electron.SaveDialogOptions { return { - defaultPath: options.defaultUri && options.defaultUri.fsPath || options.defaultFileName, + defaultPath: options.defaultUri && options.defaultUri.fsPath, buttonLabel: options.saveLabel, filters: options.filters, title: options.title diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts index 06ae69f7b9c..0ae7ef8a596 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -23,7 +23,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings'; -import { OpenLocalFileAction, OpenLocalFileFolderAction, OpenLocalFolderAction, SaveLocalFileAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { OpenLocalFileCommand, OpenLocalFileFolderCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/browser/actions/workspaceActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { isValidBasename } from 'vs/base/common/extpath'; @@ -98,7 +98,7 @@ export class RemoteFileDialog { } public async showOpenDialog(options: IOpenDialogOptions = {}): Promise { - this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems); + this.scheme = this.getScheme(options.availableFileSystems); this.userHome = await this.getUserHome(); const newOptions = await this.getOptions(options); if (!newOptions) { @@ -109,7 +109,7 @@ export class RemoteFileDialog { } public async showSaveDialog(options: ISaveDialogOptions): Promise { - this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems); + this.scheme = this.getScheme(options.availableFileSystems); this.userHome = await this.getUserHome(); this.requiresTrailing = true; const newOptions = await this.getOptions(options, true); @@ -128,9 +128,13 @@ export class RemoteFileDialog { } private getOptions(options: ISaveDialogOptions | IOpenDialogOptions, isSave: boolean = false): IOpenDialogOptions | undefined { - let defaultUri = options.defaultUri; - const filename = (defaultUri && isSave && (resources.dirname(defaultUri).path === '/')) ? resources.basename(defaultUri) : undefined; - if (!defaultUri || filename) { + let defaultUri: URI | undefined = undefined; + let filename: string | undefined = undefined; + if (options.defaultUri) { + defaultUri = (this.scheme === options.defaultUri.scheme) ? options.defaultUri : undefined; + filename = isSave ? resources.basename(options.defaultUri) : undefined; + } + if (!defaultUri) { defaultUri = this.userHome; if (filename) { defaultUri = resources.joinPath(defaultUri, filename); @@ -150,8 +154,8 @@ export class RemoteFileDialog { return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.scheme === Schemas.file ? undefined : this.remoteAuthority); } - private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string { - return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file); + private getScheme(available: string[] | undefined): string { + return available ? available[0] : Schemas.file; } private async getRemoteAgentEnvironment(): Promise { @@ -213,9 +217,9 @@ export class RemoteFileDialog { this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local'); let action; if (isSave) { - action = SaveLocalFileAction; + action = SaveLocalFileCommand; } else { - action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderAction : OpenLocalFileAction) : OpenLocalFolderAction; + action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderCommand : OpenLocalFileCommand) : OpenLocalFolderCommand; } const keybinding = this.keybindingService.lookupKeybinding(action.ID); if (keybinding) { @@ -238,6 +242,9 @@ export class RemoteFileDialog { this.filePickBox.items = []; function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) { + if (uri) { + uri = resources.removeTrailingPathSeparator(uri); + } resolve(uri); dialog.contextKey.set(false); dialog.filePickBox.dispose(); @@ -254,12 +261,8 @@ export class RemoteFileDialog { if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) { this.options.availableFileSystems.shift(); } - this.options.defaultUri = undefined; this.filePickBox.hide(); if (isSave) { - // Remove defaultUri and filters to get a consistant experience with the keybinding. - this.options.defaultUri = undefined; - this.options.filters = undefined; return this.fileDialogService.showSaveDialog(this.options).then(result => { doResolve(this, result); }); @@ -328,6 +331,7 @@ export class RemoteFileDialog { } } else { this.filePickBox.activeItems = []; + this.userEnteredPathSegment = ''; } } } catch { @@ -366,7 +370,11 @@ export class RemoteFileDialog { } private constructFullUserPath(): string { - return this.pathAppend(this.currentFolder, this.userEnteredPathSegment); + if (equalsIgnoreCase(this.filePickBox.value.substr(0, this.userEnteredPathSegment.length), this.userEnteredPathSegment)) { + return this.pathFromUri(this.currentFolder); + } else { + return this.pathAppend(this.currentFolder, this.userEnteredPathSegment); + } } private filePickBoxValue(): URI { @@ -489,11 +497,13 @@ export class RemoteFileDialog { const userPath = this.constructFullUserPath(); if (equalsIgnoreCase(userPath, value.substring(0, userPath.length))) { let hasMatch = false; - for (let i = 0; i < this.filePickBox.items.length; i++) { - const item = this.filePickBox.items[i]; - if (this.setAutoComplete(value, inputBasename, item)) { - hasMatch = true; - break; + if (inputBasename.length > this.userEnteredPathSegment.length) { + for (let i = 0; i < this.filePickBox.items.length; i++) { + const item = this.filePickBox.items[i]; + if (this.setAutoComplete(value, inputBasename, item)) { + hasMatch = true; + break; + } } } if (!hasMatch) { @@ -502,11 +512,7 @@ export class RemoteFileDialog { this.filePickBox.activeItems = []; } } else { - if (!equalsIgnoreCase(inputBasename, resources.basename(this.currentFolder))) { - this.userEnteredPathSegment = inputBasename; - } else { - this.userEnteredPathSegment = ''; - } + this.userEnteredPathSegment = inputBasename; this.autoCompletePathSegment = ''; } } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts new file mode 100644 index 00000000000..3525569601e --- /dev/null +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWindowConfiguration, IPath, IPathsToWaitFor } from 'vs/platform/windows/common/windows'; +import { IEnvironmentService, IExtensionHostDebugParams, IDebugParams } from 'vs/platform/environment/common/environment'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { ExportData } from 'vs/base/common/performance'; +import { LogLevel } from 'vs/platform/log/common/log'; +import { joinPath } from 'vs/base/common/resources'; +import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; +import { Schemas } from 'vs/base/common/network'; + +export class BrowserWindowConfiguration implements IWindowConfiguration { + + _: any[]; + + machineId: string; + windowId: number; + logLevel: LogLevel; + + mainPid: number; + + appRoot: string; + execPath: string; + isInitialStartup?: boolean; + + userEnv: IProcessEnvironment; + nodeCachedDataDir?: string; + + backupPath?: string; + + workspace?: IWorkspaceIdentifier; + folderUri?: ISingleFolderWorkspaceIdentifier; + + remoteAuthority: string; + + zoomLevel?: number; + fullscreen?: boolean; + maximized?: boolean; + highContrast?: boolean; + frameless?: boolean; + accessibilitySupport?: boolean; + partsSplashPath?: string; + + perfStartTime?: number; + perfAppReady?: number; + perfWindowLoadTime?: number; + perfEntries: ExportData; + + filesToOpenOrCreate?: IPath[]; + filesToDiff?: IPath[]; + filesToWait?: IPathsToWaitFor; + termProgram?: string; +} + +export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { + _serviceBrand: ServiceIdentifier; + + readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration(); + + constructor(configuration: IWorkbenchConstructionOptions) { + this.args = { _: [] }; + this.appRoot = '/web/'; + this.appNameLong = 'Visual Studio Code - Web'; + + this.configuration.remoteAuthority = configuration.remoteAuthority; + + this.appSettingsHome = joinPath(URI.revive(JSON.parse(document.getElementById('vscode-remote-user-data-uri')!.getAttribute('data-settings')!)), 'User'); + this.settingsResource = joinPath(this.appSettingsHome, 'settings.json'); + this.keybindingsResource = joinPath(this.appSettingsHome, 'keybindings.json'); + this.keyboardLayoutResource = joinPath(this.appSettingsHome, 'keyboardLayout.json'); + + this.logsPath = '/web/logs'; + + this.debugExtensionHost = { + port: null, + break: false + }; + + this.webviewEndpoint = configuration.webviewEndpoint; + this.untitledWorkspacesHome = URI.from({ scheme: Schemas.untitled, path: 'Workspaces' }); + } + + untitledWorkspacesHome: URI; + extensionTestsLocationURI?: URI; + args: any; + execPath: string; + cliPath: string; + appRoot: string; + userHome: string; + userDataPath: string; + appNameLong: string; + appQuality?: string; + appSettingsHome: URI; + settingsResource: URI; + keybindingsResource: URI; + keyboardLayoutResource: URI; + machineSettingsHome: URI; + machineSettingsResource: URI; + settingsSearchBuildId?: number; + settingsSearchUrl?: string; + globalStorageHome: string; + workspaceStorageHome: string; + backupHome: string; + backupWorkspacesPath: string; + workspacesHome: string; + isExtensionDevelopment: boolean; + disableExtensions: boolean | string[]; + builtinExtensionsPath: string; + extensionsPath: string; + extensionDevelopmentLocationURI?: URI[]; + extensionTestsPath?: string; + debugExtensionHost: IExtensionHostDebugParams; + debugSearch: IDebugParams; + logExtensionHostCommunication: boolean; + isBuilt: boolean; + wait: boolean; + status: boolean; + log?: string; + logsPath: string; + verbose: boolean; + skipGettingStarted: boolean; + skipReleaseNotes: boolean; + skipAddToRecentlyOpened: boolean; + mainIPCHandle: string; + sharedIPCHandle: string; + nodeCachedDataDir?: string; + installSourcePath: string; + disableUpdates: boolean; + disableCrashReporter: boolean; + driverHandle?: string; + driverVerbose: boolean; + webviewEndpoint?: string; + + get webviewResourceRoot(): string { + return this.webviewEndpoint ? this.webviewEndpoint + '/vscode-resource' : 'vscode-resource:'; + } +} diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 84c501ee1ea..fd4beaf134d 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const IWorkbenchEnvironmentService = createDecorator('environmentService'); export interface IWorkbenchEnvironmentService extends IEnvironmentService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; readonly configuration: IWindowConfiguration; } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 80418f5fad8..246e032b2bc 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -373,8 +373,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx }; } } - + perf.mark(`willHandleExtensionPoint/${extensionPoint.name}`); extensionPoint.acceptUsers(users); + perf.mark(`didHandleExtensionPoint/${extensionPoint.name}`); } private _showMessageToUser(severity: Severity, msg: string): void { diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 75ca5ef552a..f43a59b9663 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -189,13 +189,17 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: remoteExtensionHostData.globalStorageHome, - userHome: remoteExtensionHostData.userHome + userHome: remoteExtensionHostData.userHome, }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : { configuration: workspace.configuration, id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace) }, + remote: { + isRemote: true, + authority: this._initDataProvider.remoteAuthority + }, resolvedExtensions: resolvedExtensions, hostExtensions: hostExtensions, extensions: remoteExtensionHostData.extensions, @@ -203,7 +207,6 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH logLevel: this._logService.getLevel(), logsLocation: remoteExtensionHostData.extensionHostLogsPath, autoStart: true, - remoteAuthority: this._initDataProvider.remoteAuthority, }; return r; }); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 0a153bff244..3d324296b01 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -57,7 +57,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // Resources, in order they get acquired/created when .start() is called: private _namedPipeServer: Server | null; - private _inspectPort: number; + private _inspectPort: number | null; private _extensionHostProcess: ChildProcess | null; private _extensionHostConnection: Socket | null; private _messageProtocol: Promise | null; @@ -123,7 +123,10 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } if (!this._messageProtocol) { - this._messageProtocol = Promise.all([this._tryListenOnPipe(), this._tryFindDebugPort()]).then(data => { + this._messageProtocol = Promise.all([ + this._tryListenOnPipe(), + !this._environmentService.args['disable-inspect'] ? this._tryFindDebugPort() : Promise.resolve(null) + ]).then(data => { const pipeName = data[0]; const portData = data[1]; @@ -146,7 +149,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { silent: true }; - if (portData.actual) { + if (portData && portData.actual) { opts.execArgv = [ '--nolazy', (this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portData.actual @@ -213,10 +216,12 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._extensionHostProcess.on('exit', (code: number, signal: string) => this._onExtHostProcessExit(code, signal)); // Notify debugger that we are ready to attach to the process if we run a development extension - if (this._isExtensionDevHost && portData.actual && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) { - this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portData.actual); + if (portData) { + if (this._isExtensionDevHost && portData.actual && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) { + this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portData.actual); + } + this._inspectPort = portData.actual; } - this._inspectPort = portData.actual; // Help in case we fail to start it let startupTimeoutHandle: any; @@ -394,7 +399,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: URI.file(this._environmentService.globalStorageHome), - userHome: URI.file(this._environmentService.userHome) + userHome: URI.file(this._environmentService.userHome), }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { configuration: withNullAsUndefined(workspace.configuration), @@ -402,6 +407,10 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { name: this._labelService.getWorkspaceLabel(workspace), isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false }, + remote: { + authority: this._environmentService.configuration.remoteAuthority, + isRemote: false + }, resolvedExtensions: [], hostExtensions: [], extensions: extensionDescriptions, @@ -452,20 +461,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._onExit.fire([code, signal]); } - public enableInspector(): Promise { - if (this._inspectPort) { - return Promise.resolve(); - } - // send SIGUSR1 and wait a little the actual port is read from the process stdout which we - // scan here: https://github.com/Microsoft/vscode/blob/67ffab8dcd1a6752d8b62bcd13d7020101eef568/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts#L225-L240 - if (this._extensionHostProcess) { - this._extensionHostProcess.kill('SIGUSR1'); - } - return timeout(1000); - } - - public getInspectPort(): number { - return this._inspectPort; + public getInspectPort(): number | undefined { + return withNullAsUndefined(this._inspectPort); } public terminate(): void { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts index 64343563a11..611ab9aec95 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts @@ -6,13 +6,17 @@ import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { RemoteExtensionManagementChannelClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProductService } from 'vs/platform/product/common/product'; const localExtensionManagementServerAuthority: string = 'vscode-local'; @@ -25,14 +29,18 @@ export class ExtensionManagementServerService implements IExtensionManagementSer constructor( @ISharedProcessService sharedProcessService: ISharedProcessService, - @IRemoteAgentService remoteAgentService: IRemoteAgentService + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @IConfigurationService configurationService: IConfigurationService, + @IProductService productService: IProductService, + @ILogService logService: ILogService ) { const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions')); this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, authority: localExtensionManagementServerAuthority, label: localize('local', "Local") }; const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { - const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection.getChannel('extensions')); + const extensionManagementService = new RemoteExtensionManagementChannelClient(remoteAgentConnection.getChannel('extensions'), this.localExtensionManagementServer.extensionManagementService, galleryService, logService, configurationService, productService); this.remoteExtensionManagementServer = { authority: remoteAgentConnection.remoteAuthority, extensionManagementService, label: localize('remote', "Remote") }; } } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index be2007b0449..98b5cf01ba1 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -26,7 +26,7 @@ import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/ import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Schemas } from 'vs/base/common/network'; @@ -166,13 +166,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten for (let i = 0, len = _toAdd.length; i < len; i++) { const extension = _toAdd[i]; - if (extension.location.scheme !== Schemas.file) { - continue; - } - - const existingExtensionDescription = this._registry.getExtensionDescription(extension.identifier.id); - if (existingExtensionDescription) { - // this extension is already running (most likely at a different version) + if (!this._canAddExtension(extension)) { continue; } @@ -213,6 +207,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten 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._rehandleExtensionPoints(([]).concat(toAdd).concat(toRemove)); @@ -232,21 +229,28 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._doHandleExtensionPoints(extensionDescriptions); } - public canAddExtension(extension: IExtensionDescription): boolean { + public canAddExtension(extensionDescription: IExtensionDescription): boolean { + return this._canAddExtension(toExtension(extensionDescription)); + } + + public _canAddExtension(extension: IExtension): boolean { if (this._environmentService.configuration.remoteAuthority) { return false; } - if (extension.extensionLocation.scheme !== Schemas.file) { + if (extension.location.scheme !== Schemas.file) { return false; } - const extensionDescription = this._registry.getExtensionDescription(extension.identifier); + const extensionDescription = this._registry.getExtensionDescription(extension.identifier.id); if (extensionDescription) { - // ignore adding an extension which is already running and cannot be removed - if (!this._canRemoveExtension(extensionDescription)) { - return false; - } + // 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; @@ -337,7 +341,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten let extensions: Promise; if (isInitialStart) { autoStart = false; - extensions = this._extensionScanner.scannedExtensions; + extensions = this._extensionScanner.scannedExtensions.then(extensions => extensions.filter(extension => this._isEnabled(extension))); // remove disabled extensions } else { // restart case autoStart = true; diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts new file mode 100644 index 00000000000..6fd6dc821e8 --- /dev/null +++ b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { URI } from 'vs/base/common/uri'; +import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { ILogService } from 'vs/platform/log/common/log'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { values } from 'vs/base/common/map'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc'; + +export class RemoteExtensionManagementChannelClient extends ExtensionManagementChannelClient { + + _serviceBrand: any; + + constructor( + channel: IChannel, + private readonly localExtensionManagementService: IExtensionManagementService, + private readonly galleryService: IExtensionGalleryService, + private readonly logService: ILogService, + private readonly configurationService: IConfigurationService, + private readonly productService: IProductService + ) { + super(channel); + } + + async install(vsix: URI): Promise { + const local = await super.install(vsix); + await this.installUIDependenciesAndPackedExtensions(local); + return local; + } + + async installFromGallery(extension: IGalleryExtension): Promise { + const local = await this.doInstallFromGallery(extension); + await this.installUIDependenciesAndPackedExtensions(local); + return local; + } + + private async doInstallFromGallery(extension: IGalleryExtension): Promise { + try { + const local = await super.installFromGallery(extension); + return local; + } catch (error) { + try { + this.logService.error(`Error while installing '${extension.identifier.id}' extension in the remote server.`, toErrorMessage(error)); + this.logService.info(`Trying to download '${extension.identifier.id}' extension locally and install`); + const local = await this.downloadCompatibleAndInstall(extension); + this.logService.info(`Successfully installed '${extension.identifier.id}' extension`); + return local; + } catch (e) { + this.logService.error(e); + throw error; + } + } + } + + private async downloadCompatibleAndInstall(extension: IGalleryExtension): Promise { + const installed = await this.getInstalled(ExtensionType.User); + const compatible = await this.galleryService.getCompatibleExtension(extension); + if (!compatible) { + return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.version))); + } + const manifest = await this.galleryService.getManifest(compatible, CancellationToken.None); + if (manifest) { + const workspaceExtensions = await this.getAllWorkspaceDependenciesAndPackedExtensions(manifest, CancellationToken.None); + await Promise.all(workspaceExtensions.map(e => this.downloadAndInstall(e, installed))); + } + return this.downloadAndInstall(extension, installed); + } + + private async downloadAndInstall(extension: IGalleryExtension, installed: ILocalExtension[]): Promise { + const location = await this.galleryService.download(extension, installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0] ? InstallOperation.Update : InstallOperation.Install); + return super.install(URI.file(location)); + } + + private async installUIDependenciesAndPackedExtensions(local: ILocalExtension): Promise { + const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(local.manifest, CancellationToken.None); + const installed = await this.localExtensionManagementService.getInstalled(); + const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier))); + await Promise.all(toInstall.map(d => this.localExtensionManagementService.installFromGallery(d))); + } + + private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise { + const result = new Map(); + const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])]; + await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, true, token); + return values(result); + } + + private async getAllWorkspaceDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise { + const result = new Map(); + const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])]; + await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, false, token); + return values(result); + } + + private async getDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map, uiExtension: boolean, token: CancellationToken): Promise { + if (toGet.length === 0) { + return Promise.resolve(); + } + + const extensions = (await this.galleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage; + const manifests = await Promise.all(extensions.map(e => this.galleryService.getManifest(e, token))); + const extensionsManifests: IExtensionManifest[] = []; + for (let idx = 0; idx < extensions.length; idx++) { + const extension = extensions[idx]; + const manifest = manifests[idx]; + if (manifest && isUIExtension(manifest, this.productService, this.configurationService) === uiExtension) { + result.set(extension.identifier.id.toLowerCase(), extension); + extensionsManifests.push(manifest); + } + } + toGet = []; + for (const extensionManifest of extensionsManifests) { + if (isNonEmptyArray(extensionManifest.extensionDependencies)) { + for (const id of extensionManifest.extensionDependencies) { + if (!result.has(id.toLowerCase())) { + toGet.push(id); + } + } + } + if (isNonEmptyArray(extensionManifest.extensionPack)) { + for (const id of extensionManifest.extensionPack) { + if (!result.has(id.toLowerCase())) { + toGet.push(id); + } + } + } + } + return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, uiExtension, token); + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index d7b8965f8ac..1a5822e645f 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -319,10 +319,10 @@ export async function startExtensionHostProcess(): Promise { // Attempt to load uri transformer let uriTransformer: IURITransformer | null = null; - if (initData.remoteAuthority && args.uriTransformerPath) { + if (initData.remote.authority && args.uriTransformerPath) { try { const rawURITransformerFactory = require.__$__nodeRequire(args.uriTransformerPath); - const rawURITransformer = rawURITransformerFactory(initData.remoteAuthority); + const rawURITransformer = rawURITransformerFactory(initData.remote.authority); uriTransformer = new URITransformer(rawURITransformer); } catch (e) { console.error(e); diff --git a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts index e27a2a7ed46..1760b4d5fc3 100644 --- a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts +++ b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts @@ -8,7 +8,7 @@ import { IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionManagementServerService, IExtensionManagementServer, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionType, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -18,8 +18,6 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { localize } from 'vs/nls'; import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { values } from 'vs/base/common/map'; import { IProductService } from 'vs/platform/product/common/product'; export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService { @@ -134,44 +132,38 @@ export class MultiExtensionManagementService extends Disposable implements IExte return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.unzip(zipLocation, type))).then(([extensionIdentifier]) => extensionIdentifier); } - async install(vsix: URI): Promise { + async install(vsix: URI): Promise { if (this.extensionManagementServerService.remoteExtensionManagementServer) { const manifest = await getManifest(vsix.fsPath); if (isLanguagePackExtension(manifest)) { // Install on both servers - const [extensionIdentifier] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix))); - return extensionIdentifier; + const [local] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix))); + return local; } if (isUIExtension(manifest, this.productService, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); } // Install only on remote server - const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix); - // Install UI Dependencies on local server - await this.installUIDependenciesAndPackedExtensions(manifest); - return promise; + return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix); } return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); } - async installFromGallery(gallery: IGalleryExtension): Promise { + async installFromGallery(gallery: IGalleryExtension): Promise { if (this.extensionManagementServerService.remoteExtensionManagementServer) { const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); if (manifest) { if (isLanguagePackExtension(manifest)) { // Install on both servers - return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(() => undefined); + return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(([local]) => local); } if (isUIExtension(manifest, this.productService, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } // Install only on remote server - const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery); - // Install UI dependencies and packed extensions on local server - await this.installUIDependenciesAndPackedExtensions(manifest); - return promise; + return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } else { return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name)); } @@ -179,13 +171,6 @@ export class MultiExtensionManagementService extends Disposable implements IExte return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } - private async installUIDependenciesAndPackedExtensions(manifest: IExtensionManifest): Promise { - const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(manifest, CancellationToken.None); - const installed = await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getInstalled(); - const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier))); - await Promise.all(toInstall.map(d => this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d))); - } - getExtensionsReport(): Promise { return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsReport(); } @@ -193,49 +178,6 @@ export class MultiExtensionManagementService extends Disposable implements IExte private getServer(extension: ILocalExtension): IExtensionManagementServer | null { return this.extensionManagementServerService.getExtensionManagementServer(extension.location); } - - private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise { - const result = new Map(); - const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])]; - await this.getAllUIDependenciesAndPackedExtensionsRecursively(extensions, result, token); - return values(result); - } - - private async getAllUIDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map, token: CancellationToken): Promise { - if (toGet.length === 0) { - return Promise.resolve(); - } - - const extensions = (await this.extensionGalleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage; - const manifests = await Promise.all(extensions.map(e => this.extensionGalleryService.getManifest(e, token))); - const uiExtensionsManifests: IExtensionManifest[] = []; - for (let idx = 0; idx < extensions.length; idx++) { - const extension = extensions[idx]; - const manifest = manifests[idx]; - if (manifest && isUIExtension(manifest, this.productService, this.configurationService)) { - result.set(extension.identifier.id.toLowerCase(), extension); - uiExtensionsManifests.push(manifest); - } - } - toGet = []; - for (const uiExtensionManifest of uiExtensionsManifests) { - if (isNonEmptyArray(uiExtensionManifest.extensionDependencies)) { - for (const id of uiExtensionManifest.extensionDependencies) { - if (!result.has(id.toLowerCase())) { - toGet.push(id); - } - } - } - if (isNonEmptyArray(uiExtensionManifest.extensionPack)) { - for (const id of uiExtensionManifest.extensionPack) { - if (!result.has(id.toLowerCase())) { - toGet.push(id); - } - } - } - } - return this.getAllUIDependenciesAndPackedExtensionsRecursively(toGet, result, token); - } } registerSingleton(IExtensionManagementService, MultiExtensionManagementService); \ No newline at end of file diff --git a/src/vs/workbench/services/files/common/fileService.ts b/src/vs/workbench/services/files/common/fileService.ts index fd0a0d9f4e9..a788aadc1fd 100644 --- a/src/vs/workbench/services/files/common/fileService.ts +++ b/src/vs/workbench/services/files/common/fileService.ts @@ -52,7 +52,7 @@ export class FileService extends Disposable implements IFileService { const providerDisposables = new DisposableStore(); providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes)))); if (typeof provider.onDidErrorOccur === 'function') { - providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(error))); + providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error)))); } return toDisposable(() => { diff --git a/src/vs/workbench/services/files/node/diskFileSystemProvider.ts b/src/vs/workbench/services/files/node/diskFileSystemProvider.ts index 714a1dad27c..9cdf02e1438 100644 --- a/src/vs/workbench/services/files/node/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files/node/diskFileSystemProvider.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { mkdir, open, close, read, write, fdatasync } from 'fs'; +import * as os from 'os'; import { promisify } from 'util'; -import { IDisposable, Disposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; @@ -17,7 +18,7 @@ import { isEqual } from 'vs/base/common/extpath'; import { retry, ThrottledDelayer } from 'vs/base/common/async'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { localize } from 'vs/nls'; -import { IDiskFileChange, toFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, toFileChanges, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; import { FileWatcher as UnixWatcherService } from 'vs/workbench/services/files/node/watcher/unix/watcherService'; import { FileWatcher as WindowsWatcherService } from 'vs/workbench/services/files/node/watcher/win32/watcherService'; import { FileWatcher as NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/watcherService'; @@ -337,8 +338,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro //#region File Watching - private _onDidWatchErrorOccur: Emitter = this._register(new Emitter()); - get onDidErrorOccur(): Event { return this._onDidWatchErrorOccur.event; } + private _onDidWatchErrorOccur: Emitter = this._register(new Emitter()); + get onDidErrorOccur(): Event { return this._onDidWatchErrorOccur.event; } private _onDidChangeFile: Emitter = this._register(new Emitter()); get onDidChangeFile(): Event { return this._onDidChangeFile.event; } @@ -347,6 +348,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = []; private recursiveWatchRequestDelayer: ThrottledDelayer = this._register(new ThrottledDelayer(0)); + private recursiveWatcherLogLevelListener: IDisposable | undefined; + watch(resource: URI, opts: IWatchOptions): IDisposable { if (opts.recursive) { return this.watchRecursive(resource, opts.excludes); @@ -397,6 +400,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro // Dispose old dispose(this.recursiveWatcher); + this.recursiveWatcher = undefined; // Create new if we actually have folders to watch if (this.recursiveFoldersToWatch.length > 0) { @@ -404,44 +408,75 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro new( folders: { path: string, excludes: string[] }[], onChange: (changes: IDiskFileChange[]) => void, - onError: (msg: string) => void, - verboseLogging: boolean + onLogMessage: (msg: ILogMessage) => void, + verboseLogging: boolean, + watcherOptions?: { [key: string]: boolean | number | string } ): WindowsWatcherService | UnixWatcherService | NsfwWatcherService }; + let watcherOptions = undefined; - // Single Folder Watcher - if (this.recursiveFoldersToWatch.length === 1) { - if (isWindows) { - watcherImpl = WindowsWatcherService; - } else { - watcherImpl = UnixWatcherService; + if (this.forcePolling()) { + // WSL needs a polling watcher + watcherImpl = UnixWatcherService; + watcherOptions = { usePolling: true }; + } else { + // Single Folder Watcher + if (this.recursiveFoldersToWatch.length === 1) { + if (isWindows) { + watcherImpl = WindowsWatcherService; + } else { + watcherImpl = UnixWatcherService; + } } - } - // Multi Folder Watcher - else { - watcherImpl = NsfwWatcherService; + // Multi Folder Watcher + else { + watcherImpl = NsfwWatcherService; + } } // Create and start watching this.recursiveWatcher = new watcherImpl( this.recursiveFoldersToWatch, event => this._onDidChangeFile.fire(toFileChanges(event)), - error => this._onDidWatchErrorOccur.fire(new Error(error)), - this.logService.getLevel() === LogLevel.Trace + msg => { + if (msg.type === 'error') { + this._onDidWatchErrorOccur.fire(msg.message); + } + this.logService[msg.type](msg.message); + }, + this.logService.getLevel() === LogLevel.Trace, + watcherOptions ); + + if (!this.recursiveWatcherLogLevelListener) { + this.recursiveWatcherLogLevelListener = this.logService.onDidChangeLogLevel(_ => { + if (this.recursiveWatcher) { + this.recursiveWatcher.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); + } + }); + } } } } private watchNonRecursive(resource: URI): IDisposable { - return new NodeJSWatcherService( + const watcherService = new NodeJSWatcherService( this.toFilePath(resource), changes => this._onDidChangeFile.fire(toFileChanges(changes)), - error => this._onDidWatchErrorOccur.fire(new Error(error)), - info => this.logService.trace(info), + msg => { + if (msg.type === 'error') { + this._onDidWatchErrorOccur.fire(msg.message); + } + this.logService[msg.type](msg.message); + }, this.logService.getLevel() === LogLevel.Trace ); + const logLevelListener = this.logService.onDidChangeLogLevel(_ => { + watcherService.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); + }); + + return combinedDisposable(watcherService, logLevelListener); } //#endregion @@ -479,6 +514,12 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro return createFileSystemProviderError(error, code); } + + forcePolling(): boolean { + // wsl1 needs polling + return isLinux && /^[\.\-0-9]+-Microsoft/.test(os.release()); + } + //#endregion dispose(): void { @@ -486,5 +527,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro dispose(this.recursiveWatcher); this.recursiveWatcher = undefined; + + dispose(this.recursiveWatcherLogLevelListener); + this.recursiveWatcherLogLevelListener = undefined; } -} +} \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/nodejs/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nodejs/watcherService.ts index 108467214c4..4b07ee0a5c0 100644 --- a/src/vs/workbench/services/files/node/watcher/nodejs/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nodejs/watcherService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; import { Disposable } from 'vs/base/common/lifecycle'; import { statLink } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; @@ -21,8 +21,7 @@ export class FileWatcher extends Disposable { constructor( private path: string, private onFileChanges: (changes: IDiskFileChange[]) => void, - private errorLogger: (msg: string) => void, - private verboseLogger: (msg: string) => void, + private onLogMessage: (msg: ILogMessage) => void, private verboseLogging: boolean ) { super(); @@ -30,6 +29,10 @@ export class FileWatcher extends Disposable { this.startWatching(); } + setVerboseLogging(verboseLogging: boolean): void { + this.verboseLogging = verboseLogging; + } + private async startWatching(): Promise { try { const { stat, isSymbolicLink } = await statLink(this.path); @@ -78,7 +81,7 @@ export class FileWatcher extends Disposable { // Logging if (this.verboseLogging) { - this.onVerbose(`[File Watcher (node.js)] ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); + this.onVerbose(`${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); } // Handle emit through delayer to accommodate for bulk changes and thus reduce spam @@ -92,7 +95,7 @@ export class FileWatcher extends Disposable { // Logging if (this.verboseLogging) { normalizedFileChanges.forEach(event => { - this.onVerbose(`[File Watcher (node.js)] >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); + this.onVerbose(`>> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`); }); } @@ -107,13 +110,13 @@ export class FileWatcher extends Disposable { private onError(error: string): void { if (!this.isDisposed) { - this.errorLogger(error); + this.onLogMessage({ type: 'error', message: `[File Watcher (node.js)] ${error}` }); } } - private onVerbose(msg: string): void { + private onVerbose(message: string): void { if (!this.isDisposed) { - this.verboseLogger(msg); + this.onLogMessage({ type: 'trace', message: `[File Watcher (node.js)] ${message}` }); } } diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts index 0fd504f5ffb..87aae11e6bf 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -7,9 +7,9 @@ import * as glob from 'vs/base/common/glob'; import * as extpath from 'vs/base/common/extpath'; import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; -import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; import * as nsfw from 'vscode-nsfw'; -import { IWatcherService, IWatcherRequest, IWatcherOptions, IWatchError } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; +import { IWatcherService, IWatcherRequest, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; import { normalizeNFC } from 'vs/base/common/normalization'; @@ -39,11 +39,13 @@ export class NsfwWatcherService implements IWatcherService { private _verboseLogging: boolean; private enospcErrorLogged: boolean; - private _onWatchEvent = new Emitter(); + private _onWatchEvent = new Emitter(); readonly onWatchEvent = this._onWatchEvent.event; - watch(options: IWatcherOptions): Event { - this._verboseLogging = options.verboseLogging; + private _onLogMessage = new Emitter(); + readonly onLogMessage: Event = this._onLogMessage.event; + + watch(options: IWatcherOptions): Event { return this.onWatchEvent; } @@ -66,7 +68,7 @@ export class NsfwWatcherService implements IWatcherService { // See https://github.com/Microsoft/vscode/issues/7950 if (e === 'Inotify limit reached' && !this.enospcErrorLogged) { this.enospcErrorLogged = true; - this._onWatchEvent.fire({ message: 'Inotify limit reached (ENOSPC)' }); + this.error('Inotify limit reached (ENOSPC)'); } }); @@ -91,7 +93,7 @@ export class NsfwWatcherService implements IWatcherService { realBasePathLength = realBasePath.length; realBasePathDiffers = true; - console.warn(`Watcher basePath does not match version on disk and will be corrected (original: ${request.path}, real: ${realBasePath})`); + this.warn(`Watcher basePath does not match version on disk and will be corrected (original: ${request.path}, real: ${realBasePath})`); } } catch (error) { // ignore @@ -103,7 +105,7 @@ export class NsfwWatcherService implements IWatcherService { // Logging if (this._verboseLogging) { const logPath = e.action === nsfw.actions.RENAMED ? path.join(e.directory, e.oldFile || '') + ' -> ' + e.newFile : path.join(e.directory, e.file || ''); - console.log(`${e.action === nsfw.actions.CREATED ? '[CREATED]' : e.action === nsfw.actions.DELETED ? '[DELETED]' : e.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]'} ${logPath}`); + this.log(`${e.action === nsfw.actions.CREATED ? '[CREATED]' : e.action === nsfw.actions.DELETED ? '[DELETED]' : e.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]'} ${logPath}`); } // Convert nsfw event to IRawFileChange and add to queue @@ -114,13 +116,13 @@ export class NsfwWatcherService implements IWatcherService { if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.path].ignored)) { undeliveredFileEvents.push({ type: FileChangeType.DELETED, path: absolutePath }); } else if (this._verboseLogging) { - console.log(' >> ignored', absolutePath); + this.log(` >> ignored ${absolutePath}`); } absolutePath = path.join(e.directory, e.newFile || ''); if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.path].ignored)) { undeliveredFileEvents.push({ type: FileChangeType.ADDED, path: absolutePath }); } else if (this._verboseLogging) { - console.log(' >> ignored', absolutePath); + this.log(` >> ignored ${absolutePath}`); } } else { absolutePath = path.join(e.directory, e.file || ''); @@ -130,7 +132,7 @@ export class NsfwWatcherService implements IWatcherService { path: absolutePath }); } else if (this._verboseLogging) { - console.log(' >> ignored', absolutePath); + this.log(` >> ignored ${absolutePath}`); } } } @@ -160,7 +162,7 @@ export class NsfwWatcherService implements IWatcherService { // Logging if (this._verboseLogging) { res.forEach(r => { - console.log(` >> normalized ${r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${r.path}`); + this.log(` >> normalized ${r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${r.path}`); }); } @@ -190,7 +192,7 @@ export class NsfwWatcherService implements IWatcherService { // Logging if (this._verboseLogging) { - console.log(`Start watching: [${rootsToStartWatching.map(r => r.path).join(',')}]\nStop watching: [${rootsToStopWatching.join(',')}]`); + this.log(`Start watching: [${rootsToStartWatching.map(r => r.path).join(',')}]\nStop watching: [${rootsToStopWatching.join(',')}]`); } // Stop watching some roots @@ -240,4 +242,16 @@ export class NsfwWatcherService implements IWatcherService { private _isPathIgnored(absolutePath: string, ignored: glob.ParsedPattern[]): boolean { return ignored && ignored.some(i => i(absolutePath)); } + + private log(message: string) { + this._onLogMessage.fire({ type: 'trace', message: `[File Watcher (nswf)] ` + message }); + } + + private warn(message: string) { + this._onLogMessage.fire({ type: 'warn', message: `[File Watcher (nswf)] ` + message }); + } + + private error(message: string) { + this._onLogMessage.fire({ type: 'error', message: `[File Watcher (nswf)] ` + message }); + } } diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts index 3e1fb0fdb78..fa5d4b23335 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; export interface IWatcherRequest { path: string; @@ -12,16 +12,12 @@ export interface IWatcherRequest { } export interface IWatcherOptions { - verboseLogging: boolean; -} - -export interface IWatchError { - message: string; } export interface IWatcherService { - watch(options: IWatcherOptions): Event; + watch(options: IWatcherOptions): Event; setRoots(roots: IWatcherRequest[]): Promise; setVerboseLogging(enabled: boolean): Promise; + onLogMessage: Event; stop(): Promise; } \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index c073759b1d2..7e1d5edd7e1 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from './watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions } from './watcher'; import { Event } from 'vs/base/common/event'; -import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; export class WatcherChannel implements IServerChannel { @@ -15,6 +15,7 @@ export class WatcherChannel implements IServerChannel { listen(_: unknown, event: string, arg?: any): Event { switch (event) { case 'watch': return this.service.watch(arg); + case 'onLogMessage': return this.service.onLogMessage; } throw new Error(`Event not found: ${event}`); @@ -35,7 +36,7 @@ export class WatcherChannelClient implements IWatcherService { constructor(private channel: IChannel) { } - watch(options: IWatcherOptions): Event { + watch(options: IWatcherOptions): Event { return this.channel.listen('watch', options); } @@ -47,6 +48,10 @@ export class WatcherChannelClient implements IWatcherService { return this.channel.call('setRoots', roots); } + get onLogMessage(): Event { + return this.channel.listen('onLogMessage'); + } + stop(): Promise { return this.channel.call('stop'); } diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index 8924e1216c0..dd4d542ccb6 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -5,11 +5,10 @@ import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; import { WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; -import { IWatchError, IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; +import { IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export class FileWatcher extends Disposable { @@ -22,7 +21,7 @@ export class FileWatcher extends Disposable { constructor( private folders: IWatcherRequest[], private onFileChanges: (changes: IDiskFileChange[]) => void, - private errorLogger: (msg: string) => void, + private onLogMessage: (msg: ILogMessage) => void, private verboseLogging: boolean, ) { super(); @@ -42,7 +41,7 @@ export class FileWatcher extends Disposable { env: { AMD_ENTRYPOINT: 'vs/workbench/services/files/node/watcher/nsfw/watcherApp', PIPE_LOGGING: 'true', - VERBOSE_LOGGING: this.verboseLogging + VERBOSE_LOGGING: 'true' // transmit console logs from server to client } } )); @@ -52,11 +51,11 @@ export class FileWatcher extends Disposable { // that the watcher process died and we want to restart it here. we only do it a max number of times if (!this.isDisposed) { if (this.restartCounter <= FileWatcher.MAX_RESTARTS) { - this.errorLogger('[File Watcher (nsfw)] terminated unexpectedly and is restarted again...'); + this.error('terminated unexpectedly and is restarted again...'); this.restartCounter++; this.startWatching(); } else { - this.errorLogger('[File Watcher (nsfw)] failed to start after retrying for some time, giving up. Please report this as a bug report!'); + this.error('failed to start after retrying for some time, giving up. Please report this as a bug report!'); } } })); @@ -65,19 +64,28 @@ export class FileWatcher extends Disposable { const channel = getNextTickChannel(client.getChannel('watcher')); this.service = new WatcherChannelClient(channel); - const options = { verboseLogging: this.verboseLogging }; - const onWatchEvent = Event.filter(this.service.watch(options), () => !this.isDisposed); + this.service.setVerboseLogging(this.verboseLogging); - const onError = Event.filter(onWatchEvent, (e): e is IWatchError => typeof e.message === 'string'); - this._register(onError(err => this.errorLogger(`[File Watcher (nsfw)] ${err.message}`))); + const options = {}; + this._register(this.service.watch(options)(e => !this.isDisposed && this.onFileChanges(e))); - const onFileChanges = Event.filter(onWatchEvent, (e): e is IDiskFileChange[] => Array.isArray(e) && e.length > 0); - this._register(onFileChanges(e => this.onFileChanges(e))); + this._register(this.service.onLogMessage(m => this.onLogMessage(m))); // Start watching this.setFolders(this.folders); } + setVerboseLogging(verboseLogging: boolean): void { + this.verboseLogging = verboseLogging; + if (!this.isDisposed) { + this.service.setVerboseLogging(verboseLogging); + } + } + + error(message: string) { + this.onLogMessage({ type: 'error', message: `[File Watcher (nsfw)] ${message}` }); + } + setFolders(folders: IWatcherRequest[]): void { this.folders = folders; diff --git a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts index 56fdbb394f4..2302d222f57 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts @@ -14,8 +14,8 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { normalizeNFC } from 'vs/base/common/normalization'; import { realcaseSync } from 'vs/base/node/extpath'; import { isMacintosh, isLinux } from 'vs/base/common/platform'; -import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; -import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher'; +import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher'; import { Emitter, Event } from 'vs/base/common/event'; interface IWatcher { @@ -23,10 +23,6 @@ interface IWatcher { stop(): any; } -export interface IChockidarWatcherOptions { - pollingInterval?: number; -} - interface ExtendedWatcherRequest extends IWatcherRequest { parsedPattern?: glob.ParsedPattern; } @@ -40,18 +36,22 @@ export class ChokidarWatcherService implements IWatcherService { private _watcherCount: number; private _pollingInterval?: number; + private _usePolling?: boolean; private _verboseLogging: boolean; private spamCheckStartTime: number; private spamWarningLogged: boolean; private enospcErrorLogged: boolean; - private _onWatchEvent = new Emitter(); + private _onWatchEvent = new Emitter(); readonly onWatchEvent = this._onWatchEvent.event; - public watch(options: IWatcherOptions & IChockidarWatcherOptions): Event { - this._verboseLogging = options.verboseLogging; + private _onLogMessage = new Emitter(); + readonly onLogMessage: Event = this._onLogMessage.event; + + public watch(options: IWatcherOptions): Event { this._pollingInterval = options.pollingInterval; + this._usePolling = options.usePolling; this._watchers = Object.create(null); this._watcherCount = 0; return this.onWatchEvent; @@ -100,10 +100,14 @@ export class ChokidarWatcherService implements IWatcherService { private _watch(basePath: string, requests: IWatcherRequest[]): IWatcher { if (this._verboseLogging) { - console.log(`Start watching: ${basePath}]`); + this.log(`Start watching: ${basePath}]`); } const pollingInterval = this._pollingInterval || 1000; + const usePolling = this._usePolling; + if (usePolling && this._verboseLogging) { + this.log(`Use polling instead of fs.watch: Polling interval ${pollingInterval} ms`); + } const watcherOpts: chokidar.IOptions = { ignoreInitial: true, @@ -111,6 +115,7 @@ export class ChokidarWatcherService implements IWatcherService { followSymlinks: true, // this is the default of chokidar and supports file events through symlinks interval: pollingInterval, // while not used in normal cases, if any error causes chokidar to fallback to polling, increase its intervals binaryInterval: pollingInterval, + usePolling: usePolling, disableGlobbing: true // fix https://github.com/Microsoft/vscode/issues/4586 }; @@ -137,7 +142,7 @@ export class ChokidarWatcherService implements IWatcherService { const realBasePathDiffers = (basePath !== realBasePath); if (realBasePathDiffers) { - console.warn(`Watcher basePath does not match version on disk and was corrected (original: ${basePath}, real: ${realBasePath})`); + this.warn(`Watcher basePath does not match version on disk and was corrected (original: ${basePath}, real: ${realBasePath})`); } let chokidarWatcher: chokidar.FSWatcher | null = chokidar.watch(realBasePath, watcherOpts); @@ -145,7 +150,7 @@ export class ChokidarWatcherService implements IWatcherService { // Detect if for some reason the native watcher library fails to load if (isMacintosh && !chokidarWatcher.options.useFsEvents) { - console.error('Watcher is not using native fsevents library and is falling back to unefficient polling.'); + this.warn('Watcher is not using native fsevents library and is falling back to unefficient polling.'); } let undeliveredFileEvents: IDiskFileChange[] = []; @@ -156,7 +161,7 @@ export class ChokidarWatcherService implements IWatcherService { stop: () => { try { if (this._verboseLogging) { - console.log(`Stop watching: ${basePath}]`); + this.log(`Stop watching: ${basePath}]`); } if (chokidarWatcher) { chokidarWatcher.close(); @@ -168,7 +173,7 @@ export class ChokidarWatcherService implements IWatcherService { fileEventDelayer = null; } } catch (error) { - console.error(error.toString()); + this.warn('Error while stopping watcher: ' + error.toString()); } } }; @@ -218,7 +223,7 @@ export class ChokidarWatcherService implements IWatcherService { // Logging if (this._verboseLogging) { - console.log(`${eventType === FileChangeType.ADDED ? '[ADDED]' : eventType === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${path}`); + this.log(`${eventType === FileChangeType.ADDED ? '[ADDED]' : eventType === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${path}`); } // Check for spam @@ -228,7 +233,7 @@ export class ChokidarWatcherService implements IWatcherService { this.spamCheckStartTime = now; } else if (!this.spamWarningLogged && this.spamCheckStartTime + ChokidarWatcherService.EVENT_SPAM_WARNING_THRESHOLD < now) { this.spamWarningLogged = true; - console.warn(`Watcher is busy catching up with ${undeliveredFileEvents.length} file changes in 60 seconds. Latest changed path is "${event.path}"`); + this.warn(`Watcher is busy catching up with ${undeliveredFileEvents.length} file changes in 60 seconds. Latest changed path is "${event.path}"`); } // Add to buffer @@ -247,7 +252,7 @@ export class ChokidarWatcherService implements IWatcherService { // Logging if (this._verboseLogging) { res.forEach(r => { - console.log(` >> normalized ${r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${r.path}`); + this.log(` >> normalized ${r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${r.path}`); }); } @@ -268,10 +273,10 @@ export class ChokidarWatcherService implements IWatcherService { if (!this.enospcErrorLogged) { this.enospcErrorLogged = true; this.stop(); - this._onWatchEvent.fire({ message: 'Inotify limit reached (ENOSPC)' }); + this.error('Inotify limit reached (ENOSPC)'); } } else { - console.error(error.toString()); + this.warn(error.toString()); } } }); @@ -286,6 +291,18 @@ export class ChokidarWatcherService implements IWatcherService { this._watchers = Object.create(null); return Promise.resolve(); } + + private log(message: string) { + this._onLogMessage.fire({ type: 'trace', message: `[File Watcher (chockidar)] ` + message }); + } + + private warn(message: string) { + this._onLogMessage.fire({ type: 'warn', message: `[File Watcher (chockidar)] ` + message }); + } + + private error(message: string) { + this._onLogMessage.fire({ type: 'error', message: `[File Watcher (chockidar)] ` + message }); + } } function isIgnored(path: string, requests: ExtendedWatcherRequest[]): boolean { diff --git a/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts index cb79e1af37e..d28dc4adfa0 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts @@ -137,9 +137,12 @@ suite.skip('Chockidar watching', () => { service.watch(opts)(e => { if (Array.isArray(e)) { result.push(...e); - } else { - console.log('set error', e.message); - error = e.message; + } + }); + service.onLogMessage(msg => { + if (msg.type === 'error') { + console.log('set error', msg.message); + error = msg.message; } }); }); diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcher.ts b/src/vs/workbench/services/files/node/watcher/unix/watcher.ts index 3e1fb0fdb78..fc87e15803a 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcher.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; export interface IWatcherRequest { path: string; @@ -12,16 +12,14 @@ export interface IWatcherRequest { } export interface IWatcherOptions { - verboseLogging: boolean; -} - -export interface IWatchError { - message: string; + pollingInterval?: number; + usePolling?: boolean; } export interface IWatcherService { - watch(options: IWatcherOptions): Event; + watch(options: IWatcherOptions): Event; setRoots(roots: IWatcherRequest[]): Promise; setVerboseLogging(enabled: boolean): Promise; + onLogMessage: Event; stop(): Promise; } \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts index c073759b1d2..e59a0958b22 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from './watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions } from './watcher'; import { Event } from 'vs/base/common/event'; -import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; export class WatcherChannel implements IServerChannel { @@ -15,6 +15,7 @@ export class WatcherChannel implements IServerChannel { listen(_: unknown, event: string, arg?: any): Event { switch (event) { case 'watch': return this.service.watch(arg); + case 'onLogMessage': return this.service.onLogMessage; } throw new Error(`Event not found: ${event}`); @@ -35,7 +36,7 @@ export class WatcherChannelClient implements IWatcherService { constructor(private channel: IChannel) { } - watch(options: IWatcherOptions): Event { + watch(options: IWatcherOptions): Event { return this.channel.listen('watch', options); } @@ -43,6 +44,10 @@ export class WatcherChannelClient implements IWatcherService { return this.channel.call('setVerboseLogging', enable); } + get onLogMessage(): Event { + return this.channel.listen('onLogMessage'); + } + setRoots(roots: IWatcherRequest[]): Promise { return this.channel.call('setRoots', roots); } diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts index 04ce4808a85..202b505d53a 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts @@ -5,11 +5,10 @@ import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; import { WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; -import { IWatchError, IWatcherRequest } from 'vs/workbench/services/files/node/watcher/unix/watcher'; +import { IWatcherRequest, IWatcherOptions } from 'vs/workbench/services/files/node/watcher/unix/watcher'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export class FileWatcher extends Disposable { @@ -22,8 +21,9 @@ export class FileWatcher extends Disposable { constructor( private folders: IWatcherRequest[], private onFileChanges: (changes: IDiskFileChange[]) => void, - private errorLogger: (msg: string) => void, - private verboseLogging: boolean + private onLogMessage: (msg: ILogMessage) => void, + private verboseLogging: boolean, + private watcherOptions: IWatcherOptions = {} ) { super(); @@ -42,7 +42,7 @@ export class FileWatcher extends Disposable { env: { AMD_ENTRYPOINT: 'vs/workbench/services/files/node/watcher/unix/watcherApp', PIPE_LOGGING: 'true', - VERBOSE_LOGGING: this.verboseLogging + VERBOSE_LOGGING: 'true' // transmit console logs from server to client } } )); @@ -52,11 +52,11 @@ export class FileWatcher extends Disposable { // that the watcher process died and we want to restart it here. we only do it a max number of times if (!this.isDisposed) { if (this.restartCounter <= FileWatcher.MAX_RESTARTS) { - this.errorLogger('[File Watcher (chokidar)] terminated unexpectedly and is restarted again...'); + this.error('terminated unexpectedly and is restarted again...'); this.restartCounter++; this.startWatching(); } else { - this.errorLogger('[File Watcher (chokidar)] failed to start after retrying for some time, giving up. Please report this as a bug report!'); + this.error('failed to start after retrying for some time, giving up. Please report this as a bug report!'); } } })); @@ -65,19 +65,25 @@ export class FileWatcher extends Disposable { const channel = getNextTickChannel(client.getChannel('watcher')); this.service = new WatcherChannelClient(channel); - const options = { verboseLogging: this.verboseLogging }; - const onWatchEvent = Event.filter(this.service.watch(options), () => !this.isDisposed); + this.service.setVerboseLogging(this.verboseLogging); - const onError = Event.filter(onWatchEvent, (e): e is IWatchError => typeof e.message === 'string'); - this._register(onError(err => this.errorLogger(`[File Watcher (chokidar)] ${err.message}`))); + this._register(this.service.watch(this.watcherOptions)(e => !this.isDisposed && this.onFileChanges(e))); - const onFileChanges = Event.filter(onWatchEvent, (e): e is IDiskFileChange[] => Array.isArray(e) && e.length > 0); - this._register(onFileChanges(e => this.onFileChanges(e))); + this._register(this.service.onLogMessage(m => this.onLogMessage(m))); // Start watching this.service.setRoots(this.folders); } + error(message: string) { + this.onLogMessage({ type: 'error', message: `[File Watcher (chokidar)] ${message}` }); + } + + setVerboseLogging(verboseLogging: boolean): void { + this.verboseLogging = verboseLogging; + this.service.setVerboseLogging(verboseLogging); + } + setFolders(folders: IWatcherRequest[]): void { this.folders = folders; diff --git a/src/vs/workbench/services/files/node/watcher/watcher.ts b/src/vs/workbench/services/files/node/watcher/watcher.ts index cfe12d13fa9..b8cd75e6b7f 100644 --- a/src/vs/workbench/services/files/node/watcher/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/watcher.ts @@ -12,6 +12,11 @@ export interface IDiskFileChange { path: string; } +export interface ILogMessage { + type: 'trace' | 'warn' | 'error'; + message: string; +} + export function toFileChanges(changes: IDiskFileChange[]): IFileChange[] { return changes.map(change => ({ type: change.type, diff --git a/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts b/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts index 95d8797a5c6..effcbc9472d 100644 --- a/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts @@ -7,7 +7,7 @@ import * as cp from 'child_process'; import { FileChangeType } from 'vs/platform/files/common/files'; import * as decoder from 'vs/base/node/decoder'; import * as glob from 'vs/base/common/glob'; -import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export class OutOfProcessWin32FolderWatcher { @@ -25,7 +25,7 @@ export class OutOfProcessWin32FolderWatcher { private watchedFolder: string, ignored: string[], private eventCallback: (events: IDiskFileChange[]) => void, - private errorCallback: (error: string) => void, + private logCallback: (message: ILogMessage) => void, private verboseLogging: boolean ) { this.restartCounter = 0; @@ -38,7 +38,7 @@ export class OutOfProcessWin32FolderWatcher { // Logging if (this.verboseLogging) { - console.log('%c[File Watcher (C#)]', 'color: blue', `Start watching: ${watchedFolder}`); + this.log(`Start watching: ${watchedFolder}`); } this.startWatcher(); @@ -71,7 +71,7 @@ export class OutOfProcessWin32FolderWatcher { // Support ignores if (this.ignored && this.ignored.some(ignore => ignore(absolutePath))) { if (this.verboseLogging) { - console.log('%c[File Watcher (C#)]', 'color: blue', ' >> ignored', absolutePath); + this.log(absolutePath); } return; @@ -86,7 +86,7 @@ export class OutOfProcessWin32FolderWatcher { // 3 Logging else { - console.log('%c[File Watcher (C#)]', 'color: blue', eventParts[1]); + this.log(eventParts[1]); } } }); @@ -106,23 +106,31 @@ export class OutOfProcessWin32FolderWatcher { } private onError(error: Error | Buffer): void { - this.errorCallback('[File Watcher (C#)] process error: ' + error.toString()); + this.error('process error: ' + error.toString()); } private onExit(code: number, signal: string): void { if (this.handle) { // exit while not yet being disposed is unexpected! - this.errorCallback(`[File Watcher (C#)] terminated unexpectedly (code: ${code}, signal: ${signal})`); + this.error(`terminated unexpectedly (code: ${code}, signal: ${signal})`); if (this.restartCounter <= OutOfProcessWin32FolderWatcher.MAX_RESTARTS) { - this.errorCallback('[File Watcher (C#)] is restarted again...'); + this.error('is restarted again...'); this.restartCounter++; this.startWatcher(); // restart } else { - this.errorCallback('[File Watcher (C#)] Watcher failed to start after retrying for some time, giving up. Please report this as a bug report!'); + this.error('Watcher failed to start after retrying for some time, giving up. Please report this as a bug report!'); } } } + private error(message: string) { + this.logCallback({ type: 'error', message: `[File Watcher (C#)] ${message}` }); + } + + private log(message: string) { + this.logCallback({ type: 'trace', message: `[File Watcher (C#)] ${message}` }); + } + public dispose(): void { if (this.handle) { this.handle.kill(); diff --git a/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts index 0d27d68313a..bab9258c1b5 100644 --- a/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts @@ -3,24 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IDiskFileChange, ILogMessage } from 'vs/workbench/services/files/node/watcher/watcher'; import { OutOfProcessWin32FolderWatcher } from 'vs/workbench/services/files/node/watcher/win32/csharpWatcherService'; import { posix } from 'vs/base/common/path'; import { rtrim, endsWith } from 'vs/base/common/strings'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export class FileWatcher implements IDisposable { -export class FileWatcher extends Disposable { - private isDisposed: boolean; private folder: { path: string, excludes: string[] }; + private service: OutOfProcessWin32FolderWatcher | undefined = undefined; constructor( folders: { path: string, excludes: string[] }[], private onFileChanges: (changes: IDiskFileChange[]) => void, - private errorLogger: (msg: string) => void, + private onLogMessage: (msg: ILogMessage) => void, private verboseLogging: boolean ) { - super(); - this.folder = folders[0]; if (this.folder.path.indexOf('\\\\') === 0 && endsWith(this.folder.path, posix.sep)) { @@ -31,17 +30,29 @@ export class FileWatcher extends Disposable { this.folder.path = rtrim(this.folder.path, posix.sep); } - this.startWatching(); + this.service = this.startWatching(); } - private startWatching(): void { - this._register(new OutOfProcessWin32FolderWatcher( + private get isDisposed(): boolean { + return !this.service; + } + + private startWatching(): OutOfProcessWin32FolderWatcher { + return new OutOfProcessWin32FolderWatcher( this.folder.path, this.folder.excludes, events => this.onFileEvents(events), - error => this.onError(error), + message => this.onLogMessage(message), this.verboseLogging - )); + ); + } + + setVerboseLogging(verboseLogging: boolean): void { + this.verboseLogging = verboseLogging; + if (this.service) { + this.service.dispose(); + this.service = this.startWatching(); + } } private onFileEvents(events: IDiskFileChange[]): void { @@ -55,15 +66,10 @@ export class FileWatcher extends Disposable { } } - private onError(error: string): void { - if (!this.isDisposed) { - this.errorLogger(error); + dispose(): void { + if (this.service) { + this.service.dispose(); + this.service = undefined; } } - - dispose(): void { - this.isDisposed = true; - - super.dispose(); - } } \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 2270faefdbb..fcd949b0b05 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; +import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { Keybinding, ResolvedKeybinding, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { OS, OperatingSystem, isWeb } from 'vs/base/common/platform'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -42,9 +43,10 @@ import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/file import { dirname, isEqual } from 'vs/base/common/resources'; import { parse } from 'vs/base/common/json'; import * as objects from 'vs/base/common/objects'; -import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapService'; +import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { getDispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; import { isArray } from 'vs/base/common/types'; +import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/common/navigatorKeyboard'; interface ContributedKeyBinding { command: string; @@ -236,6 +238,24 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { telemetryService.publicLog('keyboardLayout', { currentKeyboardLayout: data }); + + this._register(browser.onDidChangeFullscreen(() => { + const keyboard = (navigator).keyboard; + + if (!keyboard) { + return; + } + + if (browser.isFullscreen()) { + keyboard.lock(['Escape']); + } else { + keyboard.unlock(); + } + + // update resolver which will bring back all unbound keyboard shortcuts + this._cachedResolver = null; + this._onDidUpdateKeybindings.fire({ source: KeybindingSource.User }); + })); } public _dumpDebugInfo(): string { @@ -245,6 +265,14 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return `Layout info:\n${layoutInfo}\n${mapperInfo}\n\nRaw mapping:\n${rawMapping}`; } + public _dumpDebugInfoJSON(): string { + const info = { + layout: this.keymapService.getCurrentKeyboardLayout(), + rawMapping: this.keymapService.getRawKeyboardMapping() + }; + return JSON.stringify(info, null, '\t'); + } + public customKeybindingsCount(): number { return this.userKeybindings.keybindings.length; } @@ -279,6 +307,10 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // This might be a removal keybinding item in user settings => accept it result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault); } else { + if (this._assertBrowserConflicts(keybinding, item.command)) { + continue; + } + const resolvedKeybindings = this.resolveKeybinding(keybinding); for (const resolvedKeybinding of resolvedKeybindings) { result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault); @@ -308,6 +340,77 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return result; } + private _assertBrowserConflicts(kb: Keybinding, commandId: string): boolean { + if (!isWeb) { + return false; + } + + if (browser.isStandalone) { + return false; + } + + if (browser.isFullscreen() && (navigator).keyboard) { + return false; + } + + for (let part of kb.parts) { + if (!part.metaKey && !part.altKey && !part.ctrlKey && !part.shiftKey) { + continue; + } + + const modifiersMask = KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift; + + let partModifiersMask = 0; + if (part.metaKey) { + partModifiersMask |= KeyMod.CtrlCmd; + } + + if (part.shiftKey) { + partModifiersMask |= KeyMod.Shift; + } + + if (part.altKey) { + partModifiersMask |= KeyMod.Alt; + } + + if (part.ctrlKey && OS === OperatingSystem.Macintosh) { + partModifiersMask |= KeyMod.WinCtrl; + } + + if ((partModifiersMask & modifiersMask) === KeyMod.CtrlCmd && part.keyCode === KeyCode.KEY_W) { + // console.warn('Ctrl/Cmd+W keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId); + + return true; + } + + if ((partModifiersMask & modifiersMask) === KeyMod.CtrlCmd && part.keyCode === KeyCode.KEY_N) { + // console.warn('Ctrl/Cmd+N keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId); + + return true; + } + + if ((partModifiersMask & modifiersMask) === KeyMod.CtrlCmd && part.keyCode === KeyCode.KEY_T) { + // console.warn('Ctrl/Cmd+T keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId); + + return true; + } + + if ((partModifiersMask & modifiersMask) === (KeyMod.CtrlCmd | KeyMod.Alt) && (part.keyCode === KeyCode.LeftArrow || part.keyCode === KeyCode.RightArrow)) { + // console.warn('Ctrl/Cmd+Arrow keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId); + + return true; + } + + if ((partModifiersMask & modifiersMask) === KeyMod.CtrlCmd && part.keyCode >= KeyCode.KEY_0 && part.keyCode <= KeyCode.KEY_9) { + // console.warn('Ctrl/Cmd+Num keybindings should not be used by default in web. Offender: ', kb.getHashCode(), ' for ', commandId); + + return true; + } + } + + return false; + } + public resolveKeybinding(kb: Keybinding): ResolvedKeybinding[] { return this._keyboardMapper.resolveKeybinding(kb); } @@ -432,6 +535,10 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // ignore ctrl/cmd-combination but not shift/alt-combinatios return false; } + if (event.keyCode === KeyCode.Escape) { + // https://github.com/microsoft/vscode/issues/74934 + return false; + } // consult the KeyboardMapperFactory to check the given event for // a printable value. const mapping = this.keymapService.getRawKeyboardMapping(); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayoutProvider.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayoutProvider.ts deleted file mode 100644 index 4b4bf1b564f..00000000000 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayoutProvider.ts +++ /dev/null @@ -1,276 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IKeyboardLayoutInfo } from 'vs/workbench/services/keybinding/common/keymapService'; -import { isWindows } from 'vs/base/common/platform'; - -function deserializeMapping(serializedMapping: ISerializedMapping) { - let mapping = serializedMapping; - - let ret = {}; - for (let key in mapping) { - let result: (string | number)[] = mapping[key]; - if (result.length) { - let value = result[0]; - let withShift = result[1]; - let withAltGr = result[2]; - let withShiftAltGr = result[3]; - let mask = Number(result[4]); - let vkey = result.length === 6 ? result[5] : undefined; - ret[key] = { - 'value': value, - 'vkey': vkey, - 'withShift': withShift, - 'withAltGr': withAltGr, - 'withShiftAltGr': withShiftAltGr, - 'valueIsDeadKey': (mask & 1) > 0, - 'withShiftIsDeadKey': (mask & 2) > 0, - 'withAltGrIsDeadKey': (mask & 4) > 0, - 'withShiftAltGrIsDeadKey': (mask & 8) > 0 - }; - } else { - ret[key] = { - 'value': '', - 'valueIsDeadKey': false, - 'withShift': '', - 'withShiftIsDeadKey': false, - 'withAltGr': '', - 'withAltGrIsDeadKey': false, - 'withShiftAltGr': '', - 'withShiftAltGrIsDeadKey': false - }; - } - } - - return ret; -} - -interface IKeyboardMapping { - [key: string]: { - value: string, - withShift: string; - withAltGr: string; - withShiftAltGr: string; - valueIsDeadKey?: boolean; - withShiftIsDeadKey?: boolean; - withAltGrIsDeadKey?: boolean; - withShiftAltGrIsDeadKey?: boolean; - - }; -} - -interface ISerializedMapping { - [key: string]: (string | number)[]; -} - -export class KeyboardLayoutInfo { - value: IKeyboardMapping; - - constructor(public layout: IKeyboardLayoutInfo, public secondaryLayouts: IKeyboardLayoutInfo[], keyboardMapping: ISerializedMapping) { - this.value = deserializeMapping(keyboardMapping); - } - - fuzzyEqual(other: IKeyboardMapping): boolean { - for (let key in other) { - if (isWindows && (key === 'Backslash' || key === 'KeyQ')) { - // keymap from Chromium is probably wrong. - continue; - } - if (this.value[key] === undefined) { - return false; - } - - let currentMapping = this.value[key]; - let otherMapping = other[key]; - - if (currentMapping.value !== otherMapping.value) { - return false; - } - } - - return true; - } -} - -export const EN_US = new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.US', lang: 'en' }, - [], - { - KeyA: ['a', 'A', 'å', 'Å', 0], - KeyB: ['b', 'B', '∫', 'ı', 0], - KeyC: ['c', 'C', 'ç', 'Ç', 0], - KeyD: ['d', 'D', '∂', 'Î', 0], - KeyE: ['e', 'E', '´', '´', 4], - KeyF: ['f', 'F', 'ƒ', 'Ï', 0], - KeyG: ['g', 'G', '©', '˝', 0], - KeyH: ['h', 'H', '˙', 'Ó', 0], - KeyI: ['i', 'I', 'ˆ', 'ˆ', 4], - KeyJ: ['j', 'J', '∆', 'Ô', 0], - KeyK: ['k', 'K', '˚', '', 0], - KeyL: ['l', 'L', '¬', 'Ò', 0], - KeyM: ['m', 'M', 'µ', 'Â', 0], - KeyN: ['n', 'N', '˜', '˜', 4], - KeyO: ['o', 'O', 'ø', 'Ø', 0], - KeyP: ['p', 'P', 'π', '∏', 0], - KeyQ: ['q', 'Q', 'œ', 'Œ', 0], - KeyR: ['r', 'R', '®', '‰', 0], - KeyS: ['s', 'S', 'ß', 'Í', 0], - KeyT: ['t', 'T', '†', 'ˇ', 0], - KeyU: ['u', 'U', '¨', '¨', 4], - KeyV: ['v', 'V', '√', '◊', 0], - KeyW: ['w', 'W', '∑', '„', 0], - KeyX: ['x', 'X', '≈', '˛', 0], - KeyY: ['y', 'Y', '¥', 'Á', 0], - KeyZ: ['z', 'Z', 'Ω', '¸', 0], - Digit1: ['1', '!', '¡', '⁄', 0], - Digit2: ['2', '@', '™', '€', 0], - Digit3: ['3', '#', '£', '‹', 0], - Digit4: ['4', '$', '¢', '›', 0], - Digit5: ['5', '%', '∞', 'fi', 0], - Digit6: ['6', '^', '§', 'fl', 0], - Digit7: ['7', '&', '¶', '‡', 0], - Digit8: ['8', '*', '•', '°', 0], - Digit9: ['9', '(', 'ª', '·', 0], - Digit0: ['0', ')', 'º', '‚', 0], - Enter: [], - Escape: [], - Backspace: [], - Tab: [], - Space: [' ', ' ', ' ', ' ', 0], - Minus: ['-', '_', '–', '—', 0], - Equal: ['=', '+', '≠', '±', 0], - BracketLeft: ['[', '{', '“', '”', 0], - BracketRight: [']', '}', '‘', '’', 0], - Backslash: ['\\', '|', '«', '»', 0], - Semicolon: [';', ':', '…', 'Ú', 0], - Quote: ['\'', '"', 'æ', 'Æ', 0], - Backquote: ['`', '~', '`', '`', 4], - Comma: [',', '<', '≤', '¯', 0], - Period: ['.', '>', '≥', '˘', 0], - Slash: ['/', '?', '÷', '¿', 0], - CapsLock: [], - F1: [], - F2: [], - F3: [], - F4: [], - F5: [], - F6: [], - F7: [], - F8: [], - F9: [], - F10: [], - F11: [], - F12: [], - Insert: [], - Home: [], - PageUp: [], - Delete: [], - End: [], - PageDown: [], - ArrowRight: [], - ArrowLeft: [], - ArrowDown: [], - ArrowUp: [], - NumLock: [], - NumpadDivide: ['/', '/', '/', '/', 0], - NumpadMultiply: ['*', '*', '*', '*', 0], - NumpadSubtract: ['-', '-', '-', '-', 0], - NumpadAdd: ['+', '+', '+', '+', 0], - NumpadEnter: [], - Numpad1: ['1', '1', '1', '1', 0], - Numpad2: ['2', '2', '2', '2', 0], - Numpad3: ['3', '3', '3', '3', 0], - Numpad4: ['4', '4', '4', '4', 0], - Numpad5: ['5', '5', '5', '5', 0], - Numpad6: ['6', '6', '6', '6', 0], - Numpad7: ['7', '7', '7', '7', 0], - Numpad8: ['8', '8', '8', '8', 0], - Numpad9: ['9', '9', '9', '9', 0], - Numpad0: ['0', '0', '0', '0', 0], - NumpadDecimal: ['.', '.', '.', '.', 0], - IntlBackslash: ['§', '±', '§', '±', 0], - ContextMenu: [], - NumpadEqual: ['=', '=', '=', '=', 0], - F13: [], - F14: [], - F15: [], - F16: [], - F17: [], - F18: [], - F19: [], - F20: [], - AudioVolumeMute: [], - AudioVolumeUp: ['', '=', '', '=', 0], - AudioVolumeDown: [], - NumpadComma: [], - IntlRo: [], - KanaMode: [], - IntlYen: [], - ControlLeft: [], - ShiftLeft: [], - AltLeft: [], - MetaLeft: [], - ControlRight: [], - ShiftRight: [], - AltRight: [], - MetaRight: [] - } -); - -export class KeyboardLayoutProvider { - public static readonly INSTANCE: KeyboardLayoutProvider = new KeyboardLayoutProvider(); - - private _layoutInfos: KeyboardLayoutInfo[] = []; - private _mru: KeyboardLayoutInfo[] = []; - private _active: KeyboardLayoutInfo | null; - - private constructor() { - this._active = null; - } - - registerKeyboardLayout(layout: KeyboardLayoutInfo) { - this._layoutInfos.push(layout); - this._mru = this._layoutInfos; - } - - get activeKeyboardLayout() { - return this._active; - } - - isActive(keymap: IKeyboardMapping) { - return this._active && this._active.fuzzyEqual(keymap); - } - - setActive(keymap: IKeyboardMapping) { - this._active = this.getMatchedKeyboardLayout(keymap); - - if (!this._active) { - return; - } - const index = this._mru.indexOf(this._active); - - if (index === 0) { - return; - } - - this._mru.splice(index, 1); - this._mru.unshift(this._active); - } - - getMatchedKeyboardLayout(keymap: IKeyboardMapping): KeyboardLayoutInfo | null { - // TODO go through mru list instead of _layoutInfos - for (let i = 0; i < this._mru.length; i++) { - if (this._mru[i].fuzzyEqual(keymap)) { - return this._mru[i]; - } - } - - return null; - } - - getKeyboardLayouts(): KeyboardLayoutInfo[] { - return this._layoutInfos.slice(0); - } -} diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts deleted file mode 100644 index b459f0e5a9a..00000000000 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts +++ /dev/null @@ -1,325 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IKeymapService, IKeyboardLayoutInfo, IKeyboardMapping, IWindowsKeyboardMapping } from 'vs/workbench/services/keybinding/common/keymapService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; -import { IKeyboardMapper, CachedKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; -import { OS, OperatingSystem, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { WindowsKeyboardMapper } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; -import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper'; -import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; -import { KeyCodeUtils, KeyCode } from 'vs/base/common/keyCodes'; -import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyboardLayoutProvider } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; - -export class BrowserKeyboardMapperFactory { - public static readonly INSTANCE = new BrowserKeyboardMapperFactory(); - private _layoutInfo: IKeyboardLayoutInfo | null; - private _rawMapping: IKeyboardMapping | null; - private _keyboardMapper: IKeyboardMapper | null; - private _initialized: boolean; - private readonly _onDidChangeKeyboardMapper = new Emitter(); - public readonly onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; - - private constructor() { - this._layoutInfo = null; - this._rawMapping = null; - this._keyboardMapper = null; - this._initialized = false; - - const platform = isWindows ? 'win' : isMacintosh ? 'darwin' : 'linux'; - - import('vs/workbench/services/keybinding/browser/keyboardlayouts/layout.contribution.' + platform).then(() => { - this._initialized = true; - this._onKeyboardLayoutChanged(); - }); - - if ((navigator as any).keyboard && (navigator as any).keyboard.addEventListener) { - (navigator as any).keyboard.addEventListener('layoutchange', () => { - // Update user keyboard map settings - this.getBrowserKeyMap().then((keymap: IKeyboardMapping) => { - if (KeyboardLayoutProvider.INSTANCE.isActive(keymap)) { - return; - } - - this._onKeyboardLayoutChanged(); - }); - }); - } - } - - private _onKeyboardLayoutChanged(): void { - this._updateKeyboardLayoutAsync(this._initialized); - } - - private _updateKeyboardLayoutAsync(initialized: boolean) { - if (!initialized) { - return; - } - - this.getBrowserKeyMap().then(keyMap => { - // might be false positive - if (KeyboardLayoutProvider.INSTANCE.isActive(keyMap)) { - return; - } - KeyboardLayoutProvider.INSTANCE.setActive(keyMap); - let currentKeyboardLayout = KeyboardLayoutProvider.INSTANCE.activeKeyboardLayout; - - if (currentKeyboardLayout) { - this._setKeyboardData(currentKeyboardLayout.layout, keyMap); - } - }); - } - - public getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { - if (!this._initialized) { - return new MacLinuxFallbackKeyboardMapper(OS); - } - if (dispatchConfig === DispatchConfig.KeyCode) { - // Forcefully set to use keyCode - return new MacLinuxFallbackKeyboardMapper(OS); - } - return this._keyboardMapper!; - - } - - public getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null { - if (!this._initialized) { - return null; - } - return this._layoutInfo; - } - - public validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void { - if (!this._initialized) { - return; - } - - let isCurrentKeyboard = this._validateCurrentKeyboardMapping(keyboardEvent); - - if (isCurrentKeyboard) { - return; - } - - this._updateKeyboardLayoutAsync(true); - } - - private _validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): boolean { - if (!this._initialized) { - return true; - } - - const standardKeyboardEvent = keyboardEvent as StandardKeyboardEvent; - const currentKeymap = KeyboardLayoutProvider.INSTANCE.activeKeyboardLayout; - if (!currentKeymap) { - return true; - } - - const mapping = currentKeymap.value[standardKeyboardEvent.code]; - - if (!mapping) { - return false; - } - - if (mapping.value === '') { - // we don't undetstand - if (keyboardEvent.ctrlKey || keyboardEvent.metaKey) { - setTimeout(() => { - this.getBrowserKeyMap().then((keymap: IKeyboardMapping) => { - if (KeyboardLayoutProvider.INSTANCE.isActive(keymap)) { - return; - } - - this._onKeyboardLayoutChanged(); - }); - }, 350); - } - return true; - } - - const expectedValue = standardKeyboardEvent.altKey && standardKeyboardEvent.shiftKey ? mapping.withShiftAltGr : - standardKeyboardEvent.altKey ? mapping.withAltGr : - standardKeyboardEvent.shiftKey ? mapping.withShift : mapping.value; - - const isDead = (standardKeyboardEvent.altKey && standardKeyboardEvent.shiftKey && mapping.withShiftAltGrIsDeadKey) || - (standardKeyboardEvent.altKey && mapping.withAltGrIsDeadKey) || - (standardKeyboardEvent.shiftKey && mapping.withShiftIsDeadKey) || - mapping.valueIsDeadKey; - - if (isDead && standardKeyboardEvent.browserEvent.key !== 'Dead') { - return false; - } - - // TODO, this assumption is wrong as `browserEvent.key` doesn't necessarily equal expectedValue from real keymap - if (!isDead && standardKeyboardEvent.browserEvent.key !== expectedValue) { - return false; - } - - return true; - } - - public getRawKeyboardMapping(): IKeyboardMapping | null { - if (!this._initialized) { - return null; - } - return this._rawMapping; - } - - private _setKeyboardData(layoutInfo: IKeyboardLayoutInfo, rawMapping: IKeyboardMapping): void { - this._layoutInfo = layoutInfo; - this._initialized = true; - this._rawMapping = rawMapping; - this._keyboardMapper = new CachedKeyboardMapper(BrowserKeyboardMapperFactory._createKeyboardMapper(this._layoutInfo, this._rawMapping)); - this._onDidChangeKeyboardMapper.fire(); - } - - private static _isUSStandard(rawMapping: IKeyboardMapping): boolean { - for (let key in rawMapping) { - let str = rawMapping[key].value; - let keyCode = KeyCodeUtils.fromString(str); - let usKeyCode = US_SCANCODE_MAP[key]; - - if (keyCode !== usKeyCode) { - return false; - } - - } - return true; - } - - private static _createKeyboardMapper(layoutInfo: IKeyboardLayoutInfo, rawMapping: IKeyboardMapping): IKeyboardMapper { - const isUSStandard = BrowserKeyboardMapperFactory._isUSStandard(rawMapping); - if (OS === OperatingSystem.Windows) { - return new WindowsKeyboardMapper(isUSStandard, rawMapping); - } - if (Object.keys(rawMapping).length === 0) { - // Looks like reading the mappings failed (most likely Mac + Japanese/Chinese keyboard layouts) - return new MacLinuxFallbackKeyboardMapper(OS); - } - - return new MacLinuxKeyboardMapper(isUSStandard, rawMapping, OS); - } - - async getBrowserKeyMap() { - if ((navigator as any).keyboard) { - return (navigator as any).keyboard.getLayoutMap().then((e: any) => { - let ret: IKeyboardMapping = {}; - for (let key of e) { - ret[key[0]] = { - 'value': key[1], - 'withShift': '', - 'withAltGr': '', - 'withShiftAltGr': '' - }; - } - - const matchedKeyboardLayout = KeyboardLayoutProvider.INSTANCE.getMatchedKeyboardLayout(ret); - - if (matchedKeyboardLayout) { - return matchedKeyboardLayout.value; - } - - return {}; - }); - } - - return {}; - } -} - - -export const US_SCANCODE_MAP: { [str: string]: KeyCode; } = {}; - -(function () { - function define(scanCode: string, keyCode: KeyCode): void { - US_SCANCODE_MAP[scanCode] = keyCode; - } - - define('Backquote', KeyCode.US_BACKTICK); - define('Backslash', KeyCode.US_BACKSLASH); - define('BracketLeft', KeyCode.US_OPEN_SQUARE_BRACKET); - define('BracketRight', KeyCode.US_CLOSE_SQUARE_BRACKET); - define('Comma', KeyCode.US_COMMA); - define('Digit0', KeyCode.KEY_0); - define('Digit1', KeyCode.KEY_1); - define('Digit2', KeyCode.KEY_2); - define('Digit3', KeyCode.KEY_3); - define('Digit4', KeyCode.KEY_4); - define('Digit5', KeyCode.KEY_5); - define('Digit6', KeyCode.KEY_6); - define('Digit7', KeyCode.KEY_7); - define('Digit8', KeyCode.KEY_8); - define('Digit9', KeyCode.KEY_9); - define('Equal', KeyCode.US_EQUAL); - define('IntlBackslash', KeyCode.Unknown); - define('KeyA', KeyCode.KEY_A); - define('KeyB', KeyCode.KEY_B); - define('KeyC', KeyCode.KEY_C); - define('KeyD', KeyCode.KEY_D); - define('KeyE', KeyCode.KEY_E); - define('KeyF', KeyCode.KEY_F); - define('KeyG', KeyCode.KEY_G); - define('KeyH', KeyCode.KEY_H); - define('KeyI', KeyCode.KEY_I); - define('KeyJ', KeyCode.KEY_J); - define('KeyK', KeyCode.KEY_K); - define('KeyL', KeyCode.KEY_L); - define('KeyM', KeyCode.KEY_M); - define('KeyN', KeyCode.KEY_N); - define('KeyO', KeyCode.KEY_O); - define('KeyP', KeyCode.KEY_P); - define('KeyQ', KeyCode.KEY_Q); - define('KeyR', KeyCode.KEY_R); - define('KeyS', KeyCode.KEY_S); - define('KeyT', KeyCode.KEY_T); - define('KeyU', KeyCode.KEY_U); - define('KeyV', KeyCode.KEY_V); - define('KeyW', KeyCode.KEY_W); - define('KeyX', KeyCode.KEY_X); - define('KeyY', KeyCode.KEY_Y); - define('KeyZ', KeyCode.KEY_Z); - define('Minus', KeyCode.US_MINUS); - define('Period', KeyCode.US_DOT); - define('Quote', KeyCode.US_QUOTE); - define('Semicolon', KeyCode.US_SEMICOLON); - define('Slash', KeyCode.US_SLASH); -})(); - -class BrowserKeymapService extends Disposable implements IKeymapService { - public _serviceBrand: any; - - private readonly _onDidChangeKeyboardMapper = new Emitter(); - public readonly onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; - - constructor() { - super(); - - this._register(BrowserKeyboardMapperFactory.INSTANCE.onDidChangeKeyboardMapper(() => { - this._onDidChangeKeyboardMapper.fire(); - })); - } - - getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { - return BrowserKeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig); - } - - public getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null { - return BrowserKeyboardMapperFactory.INSTANCE.getCurrentKeyboardLayout(); - } - - public getRawKeyboardMapping(): IKeyboardMapping | null { - return BrowserKeyboardMapperFactory.INSTANCE.getRawKeyboardMapping(); - } - - public validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void { - BrowserKeyboardMapperFactory.INSTANCE.validateCurrentKeyboardMapping(keyboardEvent); - } -} - -registerSingleton(IKeymapService, BrowserKeymapService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution.ts new file mode 100644 index 00000000000..89ef892047a --- /dev/null +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IKeymapInfo } from 'vs/workbench/services/keybinding/common/keymapInfo'; + +export class KeyboardLayoutContribution { + public static readonly INSTANCE: KeyboardLayoutContribution = new KeyboardLayoutContribution(); + + private _layoutInfos: IKeymapInfo[] = []; + + get layoutInfos() { + return this._layoutInfos; + } + + private constructor() { + } + + registerKeyboardLayout(layout: IKeymapInfo) { + this._layoutInfos.push(layout); + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/cz.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/cz.win.ts index 1908441eceb..c187c923cec 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/cz.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/cz.win.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000405', id: '', text: 'Czech' }, - [], - { +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000405', id: '', text: 'Czech' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -165,4 +165,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de-swiss.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de-swiss.win.ts index 44d82a33db5..b19d2935e66 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de-swiss.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de-swiss.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000807', id: '', text: 'Swiss German' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000807', id: '', text: 'Swiss German' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.darwin.ts index aa796ffa236..33d2f5d5e32 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.German', lang: 'de' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.German', lang: 'de', localizedName: 'German' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', '∫', '‹', 0], KeyC: ['c', 'C', 'ç', 'Ç', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.linux.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.linux.ts index f1f974acfd1..b4675240ef0 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.linux.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.linux.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { model: 'pc104', layout: 'de,fr,es,us', variant: ',,,', options: '', rules: 'base' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { model: 'pc104', layout: 'de', variant: '', options: '', rules: 'base' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'æ', 'Æ', 0], @@ -183,4 +184,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( MailForward: [], MailSend: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.win.ts index 5f304096a0e..46bf5981a68 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/de.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000407', id: '', text: 'German' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000407', id: '', text: 'German' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/dk.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/dk.win.ts index f75d21328df..b774622699c 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/dk.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/dk.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000406', id: '', text: 'Danish' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000406', id: '', text: 'Danish' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -166,4 +167,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-belgian.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-belgian.win.ts index 19b8f554303..89e3a27892a 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-belgian.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-belgian.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000813', id: '', text: 'Belgian (Period)' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000813', id: '', text: 'Belgian (Period)' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['q', 'Q', '', '', 0, 'VK_Q'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-ext.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-ext.darwin.ts index 3409ffa2469..a12a2338bff 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-ext.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-ext.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.USExtended', lang: 'en' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.USExtended', lang: 'en', localizedName: 'ABC - Extended' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', '¯', '̄', 4], KeyB: ['b', 'B', '˘', '̆', 4], KeyC: ['c', 'C', '¸', '̧', 4], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-in.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-in.win.ts index 7cd020c8087..a1786f42061 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-in.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-in.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00004009', id: '', text: 'India' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00004009', id: '', text: 'India' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'ā', 'Ā', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-intl.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-intl.darwin.ts index 8b740fd65d8..ceb7b67a878 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-intl.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-intl.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.USInternational-PC', lang: 'en' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.USInternational-PC', lang: 'en', localizedName: 'U.S. International - PC' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', '∫', 'ı', 0], KeyC: ['c', 'C', 'ç', 'Ç', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-intl.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-intl.win.ts index 019d09d7f67..75a2a40b805 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-intl.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-intl.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00020409', id: '0001', text: 'United States-International' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00020409', id: '0001', text: 'United States-International' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'á', 'Á', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-uk.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-uk.darwin.ts index 444c61e111a..89236337707 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-uk.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-uk.darwin.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - - { id: 'com.apple.keylayout.British', lang: 'en' }, - [], - { +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.British', lang: 'en', localizedName: 'British' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', '∫', 'ı', 0], KeyC: ['c', 'C', 'ç', 'Ç', 0], @@ -129,4 +128,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-uk.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-uk.win.ts index f8b26f6493a..0b07025fa88 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-uk.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en-uk.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000809', id: '', text: 'United Kingdom' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000809', id: '', text: 'United Kingdom' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'á', 'Á', 0, 'VK_A'], @@ -166,4 +167,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.darwin.ts index 54b5f4f135b..f0fca4a9d74 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.darwin.ts @@ -3,20 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.US', lang: 'en' }, - [ - { id: 'com.apple.keylayout.ABC', lang: 'en' }, - { id: 'com.sogou.inputmethod.sogou.pinyin', lang: 'zh-Hans' }, - { id: 'com.apple.inputmethod.Kotoeri.Roman', lang: 'en' }, - { id: 'com.apple.inputmethod.Kotoeri.Japanese', lang: 'ja' }, - { id: 'com.apple.keylayout.Australian', lang: 'en' }, - { id: 'com.apple.keylayout.Canadian', lang: 'en' }, - { id: 'com.apple.keylayout.Brazilian', lang: 'pt' }, + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.US', lang: 'en', localizedName: 'U.S.', isUSStandard: true }, + secondaryLayouts: [ + { id: 'com.apple.keylayout.ABC', lang: 'en', localizedName: 'ABC' }, + { id: 'com.sogou.inputmethod.sogou.pinyin', lang: 'zh-Hans', localizedName: 'Pinyin - Simplified' }, + { id: 'com.apple.inputmethod.Kotoeri.Roman', lang: 'en', localizedName: 'Romaji' }, + { id: 'com.apple.inputmethod.Kotoeri.Japanese', lang: 'ja', localizedName: 'Hiragana' }, + { id: 'com.apple.keylayout.Australian', lang: 'en', localizedName: 'Australian' }, + { id: 'com.apple.keylayout.Canadian', lang: 'en', localizedName: 'Canadian English' }, + { id: 'com.apple.keylayout.Brazilian', lang: 'pt', localizedName: 'Brazilian' }, ], - { + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', '∫', 'ı', 0], KeyC: ['c', 'C', 'ç', 'Ç', 0], @@ -136,4 +137,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.linux.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.linux.ts index 6c56ce8e50d..571e9164e15 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.linux.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.linux.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { model: 'pc105', layout: 'us', variant: '', options: '', rules: 'evdev' }, - [ + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { model: 'pc105', layout: 'us', variant: '', options: '', rules: 'evdev', isUSStandard: true }, + secondaryLayouts: [ { model: 'pc105', layout: 'cn', variant: '', options: '', rules: 'evdev' }, ], - { + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'a', 'A', 0], @@ -186,4 +187,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( MailSend: [] } -)); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.win.ts index cfb7709c627..3d6845dc053 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/en.win.ts @@ -3,17 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000409', id: '', text: 'US' }, - [ + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000409', id: '', text: 'US', isUSStandard: true }, + secondaryLayouts: [ { name: '00000804', id: '', text: 'Chinese (Simplified) - US Keyboard' }, { name: '00000411', id: '', text: 'Japanese' }, { name: '00000412', id: '', text: 'Korean' }, { name: '00000404', id: '', text: 'Chinese (Traditional) - US Keyboard' } ], - { + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -170,4 +171,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es-latin.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es-latin.win.ts index b7a0251226f..16531eda212 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es-latin.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es-latin.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '0000080A', id: '', text: 'Latin American' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '0000080A', id: '', text: 'Latin American' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -166,4 +167,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.darwin.ts index 8f78ab6957d..679dfb36d08 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.Spanish-ISO', lang: 'es' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.Spanish-ISO', lang: 'es', localizedName: 'Spanish - ISO' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', 'ß', '', 0], KeyC: ['c', 'C', '©', ' ', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.linux.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.linux.ts index 7d522fa9eb0..8fcff46f8d6 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.linux.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.linux.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { model: 'pc105', layout: 'es', variant: '', options: '', rules: 'evdev' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { model: 'pc105', layout: 'es', variant: '', options: '', rules: 'evdev' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'æ', 'Æ', 0], @@ -183,4 +184,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( MailForward: [], MailSend: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.win.ts index 7902f93b24b..3ac96a5dc59 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/es.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '0000040A', id: '', text: 'Spanish' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '0000040A', id: '', text: 'Spanish' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.darwin.ts index b1c4f4e95dc..fa9198b64a0 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.French', lang: 'fr' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.French', lang: 'fr', localizedName: 'French' }, + secondaryLayouts: [], + mapping: { KeyA: ['q', 'Q', '‡', 'Ω', 0], KeyB: ['b', 'B', 'ß', '∫', 0], KeyC: ['c', 'C', '©', '¢', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.linux.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.linux.ts index 683a59a9b8e..dc9488f28c3 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.linux.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.linux.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { model: 'pc104', layout: 'fr', variant: '', options: '', rules: 'base' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { model: 'pc104', layout: 'fr', variant: '', options: '', rules: 'base' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['q', 'Q', '@', 'Ω', 0], @@ -183,4 +184,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( MailForward: [], MailSend: [] } -)); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.win.ts index 6f3e5882160..c9f032d7bee 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/fr.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '0000040C', id: '', text: 'French' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '0000040C', id: '', text: 'French' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['q', 'Q', '', '', 0, 'VK_Q'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/hu.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/hu.win.ts index fb069ab561b..d6b5a4dac06 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/hu.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/hu.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '0000040E', id: '', text: 'Hungarian' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '0000040E', id: '', text: 'Hungarian' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'ä', '', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/it.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/it.darwin.ts index 4d1b13922e5..1dc97d9903f 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/it.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/it.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.Italian-Pro', lang: 'it' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.Italian-Pro', lang: 'it', localizedName: 'Italian' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', '∫', 'Í', 0], KeyC: ['c', 'C', '©', 'Á', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/it.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/it.win.ts index 88de00a2ccc..573b7b0c6c3 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/it.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/it.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000410', id: '', text: 'Italian' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000410', id: '', text: 'Italian' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/jp-roman.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/jp-roman.darwin.ts index e0efe49090f..27328f3d87b 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/jp-roman.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/jp-roman.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.google.inputmethod.Japanese.Roman', lang: 'en' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.google.inputmethod.Japanese.Roman', lang: 'en', localizedName: 'Alphanumeric (Google)' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', '¯', '̄', 4], KeyB: ['b', 'B', '˘', '̆', 4], KeyC: ['c', 'C', '¸', '̧', 4], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/jp.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/jp.darwin.ts index d50fcfb98b9..819f96ba5ca 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/jp.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/jp.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.inputmethod.Kotoeri.Japanese', lang: 'ja' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.inputmethod.Kotoeri.Japanese', lang: 'ja', localizedName: 'Hiragana' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', '∫', 'ı', 0], KeyC: ['c', 'C', 'ç', 'Ç', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ko.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ko.darwin.ts index 683a0620dd4..4219a7bd62f 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ko.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ko.darwin.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.inputmethod.Korean.2SetKorean', lang: 'ko' }, - [], - { +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.inputmethod.Korean.2SetKorean', lang: 'ko', localizedName: '2-Set Korean' }, + secondaryLayouts: [], + mapping: { KeyA: ['ㅁ', 'ㅁ', 'a', 'A', 0], KeyB: ['ㅠ', 'ㅠ', 'b', 'B', 0], KeyC: ['ㅊ', 'ㅊ', 'c', 'C', 0], @@ -129,5 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } - -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin.ts index 043b9e0261f..8de0fa205a3 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin.ts @@ -17,4 +17,6 @@ import 'vs/workbench/services/keybinding/browser/keyboardLayouts/pl.darwin'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/it.darwin'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/ru.darwin'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/pt.darwin'; -import 'vs/workbench/services/keybinding/browser/keyboardLayouts/ko.darwin'; \ No newline at end of file +import 'vs/workbench/services/keybinding/browser/keyboardLayouts/ko.darwin'; + +export { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux.ts index a0181a65942..6561501fce4 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux.ts @@ -7,4 +7,6 @@ import 'vs/workbench/services/keybinding/browser/keyboardLayouts/en.linux'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/es.linux'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/de.linux'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/fr.linux'; -import 'vs/workbench/services/keybinding/browser/keyboardLayouts/fu.linux'; +import 'vs/workbench/services/keybinding/browser/keyboardLayouts/ru.linux'; + +export { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win.ts index 5bc8dd0c530..bb85b252f90 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win.ts @@ -25,3 +25,5 @@ import 'vs/workbench/services/keybinding/browser/keyboardLayouts/hu.win'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/de-swiss.win'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/en-belgian.win'; import 'vs/workbench/services/keybinding/browser/keyboardLayouts/cz.win'; + +export { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/no.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/no.win.ts index da7134a5516..2c415e8ebff 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/no.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/no.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000414', id: '', text: 'Norwegian' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000414', id: '', text: 'Norwegian' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pl.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pl.darwin.ts index 7d97909583d..57577ba513c 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pl.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pl.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.PolishPro', lang: 'pl' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.PolishPro', lang: 'pl', localizedName: 'Polish - Pro' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'ą', 'Ą', 0], KeyB: ['b', 'B', 'ļ', 'ű', 0], KeyC: ['c', 'C', 'ć', 'Ć', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pl.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pl.win.ts index 8d91f5cf72a..a110111a83f 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pl.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pl.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000415', id: '', text: 'Polish (Programmers)' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000415', id: '', text: 'Polish (Programmers)' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'ą', 'Ą', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt-br.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt-br.win.ts index 5007c43b7d3..9bb82448a7c 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt-br.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt-br.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000416', id: '', text: 'Portuguese (Brazilian ABNT)' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000416', id: '', text: 'Portuguese (Brazilian ABNT)' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -166,4 +167,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt.darwin.ts index 6f5356ddb8f..87435fdc0ea 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.Brazilian-Pro', lang: 'pt' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.Brazilian-Pro', lang: 'pt' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', '∫', 'ı', 0], KeyC: ['c', 'C', 'ç', 'Ç', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt.win.ts index 74dfa547d94..456a537654b 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/pt.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000816', id: '', text: 'Portuguese' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000816', id: '', text: 'Portuguese' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -166,4 +167,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.darwin.ts index 7a68e2536bb..5eea5ac38f5 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.Russian', lang: 'ru' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.Russian', lang: 'ru', localizedName: 'Russian' }, + secondaryLayouts: [], + mapping: { KeyA: ['ф', 'Ф', 'ƒ', 'ƒ', 0], KeyB: ['и', 'И', 'и', 'И', 0], KeyC: ['с', 'С', '≠', '≠', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.linux.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.linux.ts index a1203ca2870..b13adb0d9ac 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.linux.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.linux.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { model: 'pc104', layout: 'ru', variant: ',', options: '', rules: 'base' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { model: 'pc104', layout: 'ru', variant: ',', options: '', rules: 'base' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['ф', 'Ф', 'ф', 'Ф', 0], @@ -183,5 +184,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( MailForward: [], MailSend: [] } - -)); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.win.ts index cfc3ac1631f..0da492a10ac 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/ru.win.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '00000419', id: '', text: 'Russian' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '00000419', id: '', text: 'Russian' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['ф', 'Ф', '', '', 0, 'VK_A'], @@ -165,4 +166,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/sv.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/sv.darwin.ts index 512d940e437..6d80477a689 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/sv.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/sv.darwin.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.keylayout.Swedish-Pro', lang: 'sv' }, - [], - { + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.keylayout.Swedish-Pro', lang: 'sv', localizedName: 'Swedish - Pro' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', '', '◊', 0], KeyB: ['b', 'B', '›', '»', 0], KeyC: ['c', 'C', 'ç', 'Ç', 0], @@ -128,4 +129,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/sv.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/sv.win.ts index 74f52703c99..c7128b5c929 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/sv.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/sv.win.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '0000041D', id: '', text: 'Swedish' }, - [ + +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '0000041D', id: '', text: 'Swedish' }, + secondaryLayouts: [ { name: '0000040B', id: '', text: 'Finnish' } ], - { + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', '', '', 0, 'VK_A'], @@ -167,5 +168,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } - -))); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/thai.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/thai.win.ts index 36a05f22e41..be85bfedd9e 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/thai.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/thai.win.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '0000041E', id: '', text: 'Thai Kedmanee' }, - [], - { +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '0000041E', id: '', text: 'Thai Kedmanee' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['ฟ', 'ฤ', '', '', 0, 'VK_A'], @@ -165,4 +165,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); +}); diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/tr.win.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/tr.win.ts index 9708d7e6622..955b03f5fc0 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/tr.win.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/tr.win.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( - { name: '0000041F', id: '', text: 'Turkish Q' }, - [], - { +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { name: '0000041F', id: '', text: 'Turkish Q' }, + secondaryLayouts: [], + mapping: { Sleep: [], WakeUp: [], KeyA: ['a', 'A', 'æ', 'Æ', 0, 'VK_A'], @@ -165,4 +165,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout((new KeyboardLayoutInfo( BrowserRefresh: [], BrowserFavorites: [] } -))); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/zh-hans.darwin.ts b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/zh-hans.darwin.ts index 30d7246fa0d..49d1f60ae0d 100644 --- a/src/vs/workbench/services/keybinding/browser/keyboardLayouts/zh-hans.darwin.ts +++ b/src/vs/workbench/services/keybinding/browser/keyboardLayouts/zh-hans.darwin.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyboardLayoutProvider, KeyboardLayoutInfo } from 'vs/workbench/services/keybinding/browser/keyboardLayoutProvider'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; -KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( - { id: 'com.apple.inputmethod.SCIM.ITABC', lang: 'zh-Hans' }, - [], - { +KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({ + layout: { id: 'com.apple.inputmethod.SCIM.ITABC', lang: 'zh-Hans', localizedName: '搜狗拼音' }, + secondaryLayouts: [], + mapping: { KeyA: ['a', 'A', 'å', 'Å', 0], KeyB: ['b', 'B', '∫', 'ı', 0], KeyC: ['c', 'C', 'ç', 'Ç', 0], @@ -128,5 +128,4 @@ KeyboardLayoutProvider.INSTANCE.registerKeyboardLayout(new KeyboardLayoutInfo( AltRight: [], MetaRight: [] } - -)); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/browser/keymapService.ts b/src/vs/workbench/services/keybinding/browser/keymapService.ts new file mode 100644 index 00000000000..cff794267bf --- /dev/null +++ b/src/vs/workbench/services/keybinding/browser/keymapService.ts @@ -0,0 +1,634 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, toDisposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IKeymapService, IKeyboardLayoutInfo, IKeyboardMapping, IWindowsKeyboardMapping, KeymapInfo, IRawMixedKeyboardMapping, getKeyboardLayoutId, IKeymapInfo } from 'vs/workbench/services/keybinding/common/keymapInfo'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; +import { IKeyboardMapper, CachedKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { OS, OperatingSystem, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { WindowsKeyboardMapper } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; +import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper'; +import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; +import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { URI } from 'vs/base/common/uri'; +import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { dirname, isEqual } from 'vs/base/common/resources'; +import { parse } from 'vs/base/common/json'; +import * as objects from 'vs/base/common/objects'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ConfigExtensions, IConfigurationRegistry, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/common/navigatorKeyboard'; + +export class BrowserKeyboardMapperFactoryBase { + // keyboard mapper + protected _initialized: boolean; + protected _keyboardMapper: IKeyboardMapper | null; + private readonly _onDidChangeKeyboardMapper = new Emitter(); + public readonly onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; + + // keymap infos + protected _keymapInfos: KeymapInfo[]; + protected _mru: KeymapInfo[]; + private _activeKeymapInfo: KeymapInfo | null; + + get activeKeymap(): KeymapInfo | null { + return this._activeKeymapInfo; + } + + get keymapInfos(): KeymapInfo[] { + return this._keymapInfos; + } + + get activeKeyboardLayout(): IKeyboardLayoutInfo | null { + if (!this._initialized) { + return null; + } + + return this._activeKeymapInfo && this._activeKeymapInfo.layout; + } + + get activeKeyMapping(): IKeyboardMapping | null { + if (!this._initialized) { + return null; + } + + return this._activeKeymapInfo && this._activeKeymapInfo.mapping; + } + + get keyboardLayouts(): IKeyboardLayoutInfo[] { + return this._keymapInfos.map(keymapInfo => keymapInfo.layout); + } + + protected constructor() { + this._keyboardMapper = null; + this._initialized = false; + this._keymapInfos = []; + this._mru = []; + this._activeKeymapInfo = null; + + if ((navigator).keyboard && (navigator).keyboard.addEventListener) { + (navigator).keyboard.addEventListener!('layoutchange', () => { + // Update user keyboard map settings + this._getBrowserKeyMapping().then((mapping: IKeyboardMapping | null) => { + if (this.isKeyMappingActive(mapping)) { + return; + } + + this.onKeyboardLayoutChanged(); + }); + }); + } + } + + registerKeyboardLayout(layout: KeymapInfo) { + this._keymapInfos.push(layout); + this._mru = this._keymapInfos; + } + + removeKeyboardLayout(layout: KeymapInfo): void { + let index = this._mru.indexOf(layout); + this._mru.splice(index, 1); + index = this._keymapInfos.indexOf(layout); + this._keymapInfos.splice(index, 1); + } + + getMatchedKeymapInfo(keyMapping: IKeyboardMapping | null): KeymapInfo | null { + if (!keyMapping) { + return null; + } + + let usStandard = this.getUSStandardLayout(); + + if (usStandard) { + let maxScore = usStandard.getScore(keyMapping); + if (maxScore === 0) { + return usStandard; + } + + let result = usStandard; + for (let i = 0; i < this._mru.length; i++) { + let score = this._mru[i].getScore(keyMapping); + if (score > maxScore) { + if (score === 0) { + return this._mru[i]; + } + + maxScore = score; + result = this._mru[i]; + } + } + + return result; + } + + for (let i = 0; i < this._mru.length; i++) { + if (this._mru[i].fuzzyEqual(keyMapping)) { + return this._mru[i]; + } + } + + return null; + } + + getUSStandardLayout() { + const usStandardLayouts = this._mru.filter(layout => layout.layout.isUSStandard); + + if (usStandardLayouts.length) { + return usStandardLayouts[0]; + } + + return null; + } + + isKeyMappingActive(keymap: IKeyboardMapping | null) { + return this._activeKeymapInfo && keymap && this._activeKeymapInfo.fuzzyEqual(keymap); + } + + setUSKeyboardLayout() { + this._activeKeymapInfo = this.getUSStandardLayout(); + } + + setActiveKeyMapping(keymap: IKeyboardMapping | null) { + let matchedKeyboardLayout = this.getMatchedKeymapInfo(keymap); + if (matchedKeyboardLayout) { + if (!this._activeKeymapInfo) { + this._activeKeymapInfo = matchedKeyboardLayout; + } else if (keymap) { + if (matchedKeyboardLayout.getScore(keymap) > this._activeKeymapInfo.getScore(keymap)) { + this._activeKeymapInfo = matchedKeyboardLayout; + } + } + } + + if (!this._activeKeymapInfo) { + this._activeKeymapInfo = this.getUSStandardLayout(); + } + + if (!this._activeKeymapInfo) { + return; + } + + const index = this._mru.indexOf(this._activeKeymapInfo); + + this._mru.splice(index, 1); + this._mru.unshift(this._activeKeymapInfo); + + this._setKeyboardData(this._activeKeymapInfo); + } + + setActiveKeymapInfo(keymapInfo: KeymapInfo) { + this._activeKeymapInfo = keymapInfo; + + const index = this._mru.indexOf(this._activeKeymapInfo); + + if (index === 0) { + return; + } + + this._mru.splice(index, 1); + this._mru.unshift(this._activeKeymapInfo); + + this._setKeyboardData(this._activeKeymapInfo); + } + + public onKeyboardLayoutChanged(): void { + this._updateKeyboardLayoutAsync(this._initialized); + } + + private _updateKeyboardLayoutAsync(initialized: boolean, keyboardEvent?: IKeyboardEvent) { + if (!initialized) { + return; + } + + this._getBrowserKeyMapping(keyboardEvent).then(keyMap => { + // might be false positive + if (this.isKeyMappingActive(keyMap)) { + return; + } + this.setActiveKeyMapping(keyMap); + }); + } + + public getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { + if (!this._initialized) { + return new MacLinuxFallbackKeyboardMapper(OS); + } + if (dispatchConfig === DispatchConfig.KeyCode) { + // Forcefully set to use keyCode + return new MacLinuxFallbackKeyboardMapper(OS); + } + return this._keyboardMapper!; + } + + public validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void { + if (!this._initialized) { + return; + } + + let isCurrentKeyboard = this._validateCurrentKeyboardMapping(keyboardEvent); + + if (isCurrentKeyboard) { + return; + } + + this._updateKeyboardLayoutAsync(true, keyboardEvent); + } + + public setKeyboardLayout(layoutName: string) { + let matchedLayouts: KeymapInfo[] = this.keymapInfos.filter(keymapInfo => getKeyboardLayoutId(keymapInfo.layout) === layoutName); + + if (matchedLayouts.length > 0) { + this.setActiveKeymapInfo(matchedLayouts[0]); + } + } + + private _setKeyboardData(keymapInfo: KeymapInfo): void { + this._initialized = true; + + this._keyboardMapper = new CachedKeyboardMapper(BrowserKeyboardMapperFactory._createKeyboardMapper(keymapInfo)); + this._onDidChangeKeyboardMapper.fire(); + } + + private static _createKeyboardMapper(keymapInfo: KeymapInfo): IKeyboardMapper { + let rawMapping = keymapInfo.mapping; + const isUSStandard = !!keymapInfo.layout.isUSStandard; + if (OS === OperatingSystem.Windows) { + return new WindowsKeyboardMapper(isUSStandard, rawMapping); + } + if (Object.keys(rawMapping).length === 0) { + // Looks like reading the mappings failed (most likely Mac + Japanese/Chinese keyboard layouts) + return new MacLinuxFallbackKeyboardMapper(OS); + } + + return new MacLinuxKeyboardMapper(isUSStandard, rawMapping, OS); + } + + //#region Browser API + private _validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): boolean { + if (!this._initialized) { + return true; + } + + const standardKeyboardEvent = keyboardEvent as StandardKeyboardEvent; + const currentKeymap = this._activeKeymapInfo; + if (!currentKeymap) { + return true; + } + + const mapping = currentKeymap.mapping[standardKeyboardEvent.code]; + + if (!mapping) { + return false; + } + + if (mapping.value === '') { + // we don't undetstand + if (keyboardEvent.ctrlKey || keyboardEvent.metaKey) { + setTimeout(() => { + this._getBrowserKeyMapping().then((keymap: IKeyboardMapping) => { + if (this.isKeyMappingActive(keymap)) { + return; + } + + this.onKeyboardLayoutChanged(); + }); + }, 350); + } + return true; + } + + const expectedValue = standardKeyboardEvent.altKey && standardKeyboardEvent.shiftKey ? mapping.withShiftAltGr : + standardKeyboardEvent.altKey ? mapping.withAltGr : + standardKeyboardEvent.shiftKey ? mapping.withShift : mapping.value; + + const isDead = (standardKeyboardEvent.altKey && standardKeyboardEvent.shiftKey && mapping.withShiftAltGrIsDeadKey) || + (standardKeyboardEvent.altKey && mapping.withAltGrIsDeadKey) || + (standardKeyboardEvent.shiftKey && mapping.withShiftIsDeadKey) || + mapping.valueIsDeadKey; + + if (isDead && standardKeyboardEvent.browserEvent.key !== 'Dead') { + return false; + } + + // TODO, this assumption is wrong as `browserEvent.key` doesn't necessarily equal expectedValue from real keymap + if (!isDead && standardKeyboardEvent.browserEvent.key !== expectedValue) { + return false; + } + + return true; + } + + private async _getBrowserKeyMapping(keyboardEvent?: IKeyboardEvent): Promise { + if ((navigator as any).keyboard) { + try { + return (navigator as any).keyboard.getLayoutMap().then((e: any) => { + let ret: IKeyboardMapping = {}; + for (let key of e) { + ret[key[0]] = { + 'value': key[1], + 'withShift': '', + 'withAltGr': '', + 'withShiftAltGr': '' + }; + } + + const matchedKeyboardLayout = this.getMatchedKeymapInfo(ret); + + if (matchedKeyboardLayout) { + return matchedKeyboardLayout.mapping; + } + + return null; + }); + } catch { + // getLayoutMap can throw if invoked from a nested browsing context + } + } else if (keyboardEvent && !keyboardEvent.shiftKey && !keyboardEvent.altKey && !keyboardEvent.metaKey && !keyboardEvent.metaKey) { + let ret: IKeyboardMapping = {}; + const standardKeyboardEvent = keyboardEvent as StandardKeyboardEvent; + ret[standardKeyboardEvent.browserEvent.code] = { + 'value': standardKeyboardEvent.browserEvent.key, + 'withShift': '', + 'withAltGr': '', + 'withShiftAltGr': '' + }; + + const matchedKeyboardLayout = this.getMatchedKeymapInfo(ret); + + if (matchedKeyboardLayout) { + return ret; + } + + return null; + } + + return null; + } + + //#endregion +} + +export class BrowserKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBase { + public static readonly INSTANCE = new BrowserKeyboardMapperFactory(); + // keyboard mapper + + private constructor() { + super(); + + const platform = isWindows ? 'win' : isMacintosh ? 'darwin' : 'linux'; + + import('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.' + platform).then((m) => { + let keymapInfos: IKeymapInfo[] = m.KeyboardLayoutContribution.INSTANCE.layoutInfos; + this._keymapInfos.push(...keymapInfos.map(info => (new KeymapInfo(info.layout, info.secondaryLayouts, info.mapping, info.isUserKeyboardLayout)))); + this._mru = this._keymapInfos; + this._initialized = true; + this.onKeyboardLayoutChanged(); + }); + } +} + +class UserKeyboardLayout extends Disposable { + private readonly reloadConfigurationScheduler: RunOnceScheduler; + protected readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + private fileWatcherDisposable: IDisposable = Disposable.None; + private directoryWatcherDisposable: IDisposable = Disposable.None; + + private _keyboardLayout: KeymapInfo | null; + get keyboardLayout(): KeymapInfo | null { return this._keyboardLayout; } + + constructor( + private readonly keyboardLayoutResource: URI, + private readonly fileService: IFileService + ) { + super(); + + this._keyboardLayout = null; + + this._register(fileService.onFileChanges(e => this.handleFileEvents(e))); + this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(changed => { + if (changed) { + this._onDidChange.fire(); + } + }), 50)); + + this._register(toDisposable(() => { + this.stopWatchingResource(); + this.stopWatchingDirectory(); + })); + } + + async initialize(): Promise { + const exists = await this.fileService.exists(this.keyboardLayoutResource); + this.onResourceExists(exists); + await this.reload(); + } + + private async reload(): Promise { + const existing = this._keyboardLayout; + try { + const content = await this.fileService.readFile(this.keyboardLayoutResource); + const value = parse(content.value.toString()); + const layoutInfo = value.layout; + const mappings = value.rawMapping; + this._keyboardLayout = KeymapInfo.createKeyboardLayoutFromDebugInfo(layoutInfo, mappings, true); + } catch (e) { + this._keyboardLayout = null; + } + + return existing ? !objects.equals(existing, this._keyboardLayout) : true; + } + + private watchResource(): void { + this.fileWatcherDisposable = this.fileService.watch(this.keyboardLayoutResource); + } + + private watchDirectory(): void { + const directory = dirname(this.keyboardLayoutResource); + this.directoryWatcherDisposable = this.fileService.watch(directory); + } + + private stopWatchingResource(): void { + this.fileWatcherDisposable.dispose(); + this.fileWatcherDisposable = Disposable.None; + } + + private stopWatchingDirectory(): void { + this.directoryWatcherDisposable.dispose(); + this.directoryWatcherDisposable = Disposable.None; + } + + private async handleFileEvents(event: FileChangesEvent): Promise { + const events = event.changes; + + let affectedByChanges = false; + + // Find changes that affect the resource + for (const event of events) { + affectedByChanges = isEqual(this.keyboardLayoutResource, event.resource); + if (affectedByChanges) { + if (event.type === FileChangeType.ADDED) { + this.onResourceExists(true); + } else if (event.type === FileChangeType.DELETED) { + this.onResourceExists(false); + } + break; + } + } + + if (affectedByChanges) { + this.reloadConfigurationScheduler.schedule(); + } + } + + private onResourceExists(exists: boolean): void { + if (exists) { + this.stopWatchingDirectory(); + this.watchResource(); + } else { + this.stopWatchingResource(); + this.watchDirectory(); + } + } +} + +class BrowserKeymapService extends Disposable implements IKeymapService { + public _serviceBrand: any; + + private readonly _onDidChangeKeyboardMapper = new Emitter(); + public readonly onDidChangeKeyboardMapper: Event = this._onDidChangeKeyboardMapper.event; + + private _userKeyboardLayout: UserKeyboardLayout; + + private readonly layoutChangeListener = this._register(new MutableDisposable()); + + constructor( + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IConfigurationService private configurationService: IConfigurationService, + ) { + super(); + const keyboardConfig = configurationService.getValue<{ layout: string }>('keyboard'); + const layout = keyboardConfig.layout; + + this.registerKeyboardListener(); + + if (layout && layout !== 'autodetect') { + // set keyboard layout + BrowserKeyboardMapperFactory.INSTANCE.setKeyboardLayout(layout); + } + + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectedKeys.indexOf('keyboard.layout') >= 0) { + const keyboardConfig = configurationService.getValue<{ layout: string }>('keyboard'); + const layout = keyboardConfig.layout; + + if (layout === 'autodetect') { + this.registerKeyboardListener(); + BrowserKeyboardMapperFactory.INSTANCE.onKeyboardLayoutChanged(); + } else { + BrowserKeyboardMapperFactory.INSTANCE.setKeyboardLayout(layout); + this.layoutChangeListener.clear(); + } + } + })); + + this._userKeyboardLayout = new UserKeyboardLayout(environmentService.keyboardLayoutResource, fileService); + this._userKeyboardLayout.initialize().then(() => { + if (this._userKeyboardLayout.keyboardLayout) { + BrowserKeyboardMapperFactory.INSTANCE.registerKeyboardLayout(this._userKeyboardLayout.keyboardLayout); + + this.setUserKeyboardLayoutIfMatched(); + } + }); + + this._register(this._userKeyboardLayout.onDidChange(() => { + let userKeyboardLayouts = BrowserKeyboardMapperFactory.INSTANCE.keymapInfos.filter(layout => layout.isUserKeyboardLayout); + + if (userKeyboardLayouts.length) { + if (this._userKeyboardLayout.keyboardLayout) { + userKeyboardLayouts[0].update(this._userKeyboardLayout.keyboardLayout); + } else { + BrowserKeyboardMapperFactory.INSTANCE.removeKeyboardLayout(userKeyboardLayouts[0]); + } + } else { + if (this._userKeyboardLayout.keyboardLayout) { + BrowserKeyboardMapperFactory.INSTANCE.registerKeyboardLayout(this._userKeyboardLayout.keyboardLayout); + } + } + + this.setUserKeyboardLayoutIfMatched(); + })); + } + + setUserKeyboardLayoutIfMatched() { + const keyboardConfig = this.configurationService.getValue<{ layout: string }>('keyboard'); + const layout = keyboardConfig.layout; + + if (layout && this._userKeyboardLayout.keyboardLayout) { + if (getKeyboardLayoutId(this._userKeyboardLayout.keyboardLayout.layout) === layout && BrowserKeyboardMapperFactory.INSTANCE.activeKeymap) { + + if (!this._userKeyboardLayout.keyboardLayout.equal(BrowserKeyboardMapperFactory.INSTANCE.activeKeymap)) { + BrowserKeyboardMapperFactory.INSTANCE.setActiveKeymapInfo(this._userKeyboardLayout.keyboardLayout); + } + } + } + } + + registerKeyboardListener() { + this.layoutChangeListener.value = BrowserKeyboardMapperFactory.INSTANCE.onDidChangeKeyboardMapper(() => { + this._onDidChangeKeyboardMapper.fire(); + }); + } + + getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper { + return BrowserKeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig); + } + + public getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null { + return BrowserKeyboardMapperFactory.INSTANCE.activeKeyboardLayout; + } + + public getAllKeyboardLayouts(): IKeyboardLayoutInfo[] { + return BrowserKeyboardMapperFactory.INSTANCE.keyboardLayouts; + } + + public getRawKeyboardMapping(): IKeyboardMapping | null { + return BrowserKeyboardMapperFactory.INSTANCE.activeKeyMapping; + } + + public validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void { + BrowserKeyboardMapperFactory.INSTANCE.validateCurrentKeyboardMapping(keyboardEvent); + } +} + +registerSingleton(IKeymapService, BrowserKeymapService, true); + +// Configuration +const configurationRegistry = Registry.as(ConfigExtensions.Configuration); +const keyboardConfiguration: IConfigurationNode = { + 'id': 'keyboard', + 'order': 15, + 'type': 'object', + 'title': nls.localize('keyboardConfigurationTitle', "Keyboard"), + 'overridable': true, + 'properties': { + 'keyboard.layout': { + 'type': 'string', + 'default': 'autodetect', + 'description': nls.localize('keyboard.layout.config', "Control the keyboard layout used in web.") + } + } +}; + +configurationRegistry.registerConfiguration(keyboardConfiguration); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/common/keymapInfo.ts b/src/vs/workbench/services/keybinding/common/keymapInfo.ts new file mode 100644 index 00000000000..edea5fd675c --- /dev/null +++ b/src/vs/workbench/services/keybinding/common/keymapInfo.ts @@ -0,0 +1,342 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { isWindows, isLinux } from 'vs/base/common/platform'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; +import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; + + +export interface IWindowsKeyMapping { + vkey: string; + value: string; + withShift: string; + withAltGr: string; + withShiftAltGr: string; +} +export interface IWindowsKeyboardMapping { + [code: string]: IWindowsKeyMapping; +} +export interface ILinuxKeyMapping { + value: string; + withShift: string; + withAltGr: string; + withShiftAltGr: string; +} +export interface ILinuxKeyboardMapping { + [code: string]: ILinuxKeyMapping; +} +export interface IMacKeyMapping { + value: string; + withShift: string; + withAltGr: string; + withShiftAltGr: string; + valueIsDeadKey: boolean; + withShiftIsDeadKey: boolean; + withAltGrIsDeadKey: boolean; + withShiftAltGrIsDeadKey: boolean; +} +export interface IMacKeyboardMapping { + [code: string]: IMacKeyMapping; +} + +export type IKeyboardMapping = IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping; + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface IWindowsKeyboardLayoutInfo { + name: string; + id: string; + text: string; +} + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface ILinuxKeyboardLayoutInfo { + model: string; + layout: string; + variant: string; + options: string; + rules: string; +} + +/* __GDPR__FRAGMENT__ + "IKeyboardLayoutInfo" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "localizedName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +export interface IMacKeyboardLayoutInfo { + id: string; + lang: string; + localizedName?: string; +} + +export type IKeyboardLayoutInfo = (IWindowsKeyboardLayoutInfo | ILinuxKeyboardLayoutInfo | IMacKeyboardLayoutInfo) & { isUserKeyboardLayout?: boolean; isUSStandard?: true }; + +export const IKeymapService = createDecorator('keymapService'); + +export interface IKeymapService { + _serviceBrand: ServiceIdentifier; + onDidChangeKeyboardMapper: Event; + getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper; + getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null; + getAllKeyboardLayouts(): IKeyboardLayoutInfo[]; + getRawKeyboardMapping(): IKeyboardMapping | null; + validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void; +} + +export function areKeyboardLayoutsEqual(a: IKeyboardLayoutInfo | null, b: IKeyboardLayoutInfo | null): boolean { + if (!a || !b) { + return false; + } + + if ((a).name && (b).name && (a).name === (b).name) { + return true; + } + + if ((a).id && (b).id && (a).id === (b).id) { + return true; + } + + if ((a).model && + (b).model && + (a).model === (b).model && + (a).layout === (b).layout + ) { + return true; + } + + return false; +} + +export function parseKeyboardLayoutDescription(layout: IKeyboardLayoutInfo | null): { label: string, description: string } { + if (!layout) { + return { label: '', description: '' }; + } + + if ((layout).name) { + // windows + let windowsLayout = layout; + return { + label: windowsLayout.text, + description: '' + }; + } + + if ((layout).id) { + let macLayout = layout; + if (macLayout.localizedName) { + return { + label: macLayout.localizedName, + description: '' + }; + } + + if (/^com\.apple\.keylayout\./.test(macLayout.id)) { + return { + label: macLayout.id.replace(/^com\.apple\.keylayout\./, '').replace(/-/, ' '), + description: '' + }; + } + if (/^.*inputmethod\./.test(macLayout.id)) { + return { + label: macLayout.id.replace(/^.*inputmethod\./, '').replace(/[-\.]/, ' '), + description: `Input Method (${macLayout.lang})` + }; + } + + return { + label: macLayout.lang, + description: '' + }; + } + + let linuxLayout = layout; + + return { + label: linuxLayout.layout, + description: '' + }; +} + +export function getKeyboardLayoutId(layout: IKeyboardLayoutInfo): string { + if ((layout).name) { + return (layout).name; + } + + if ((layout).id) { + return (layout).id; + } + + return (layout).layout; +} + +function deserializeMapping(serializedMapping: ISerializedMapping) { + let mapping = serializedMapping; + + let ret = {}; + for (let key in mapping) { + let result: (string | number)[] = mapping[key]; + if (result.length) { + let value = result[0]; + let withShift = result[1]; + let withAltGr = result[2]; + let withShiftAltGr = result[3]; + let mask = Number(result[4]); + let vkey = result.length === 6 ? result[5] : undefined; + ret[key] = { + 'value': value, + 'vkey': vkey, + 'withShift': withShift, + 'withAltGr': withAltGr, + 'withShiftAltGr': withShiftAltGr, + 'valueIsDeadKey': (mask & 1) > 0, + 'withShiftIsDeadKey': (mask & 2) > 0, + 'withAltGrIsDeadKey': (mask & 4) > 0, + 'withShiftAltGrIsDeadKey': (mask & 8) > 0 + }; + } else { + ret[key] = { + 'value': '', + 'valueIsDeadKey': false, + 'withShift': '', + 'withShiftIsDeadKey': false, + 'withAltGr': '', + 'withAltGrIsDeadKey': false, + 'withShiftAltGr': '', + 'withShiftAltGrIsDeadKey': false + }; + } + } + + return ret; +} + +export interface IRawMixedKeyboardMapping { + [key: string]: { + value: string, + withShift: string; + withAltGr: string; + withShiftAltGr: string; + valueIsDeadKey?: boolean; + withShiftIsDeadKey?: boolean; + withAltGrIsDeadKey?: boolean; + withShiftAltGrIsDeadKey?: boolean; + + }; +} + +interface ISerializedMapping { + [key: string]: (string | number)[]; +} + +export interface IKeymapInfo { + layout: IKeyboardLayoutInfo; + secondaryLayouts: IKeyboardLayoutInfo[]; + mapping: ISerializedMapping; + isUserKeyboardLayout?: boolean; +} + +export class KeymapInfo { + mapping: IRawMixedKeyboardMapping; + isUserKeyboardLayout: boolean; + + constructor(public layout: IKeyboardLayoutInfo, public secondaryLayouts: IKeyboardLayoutInfo[], keyboardMapping: ISerializedMapping, isUserKeyboardLayout?: boolean) { + this.mapping = deserializeMapping(keyboardMapping); + this.isUserKeyboardLayout = !!isUserKeyboardLayout; + this.layout.isUserKeyboardLayout = !!isUserKeyboardLayout; + } + + static createKeyboardLayoutFromDebugInfo(layout: IKeyboardLayoutInfo, value: IRawMixedKeyboardMapping, isUserKeyboardLayout?: boolean): KeymapInfo { + let keyboardLayoutInfo = new KeymapInfo(layout, [], {}, true); + keyboardLayoutInfo.mapping = value; + return keyboardLayoutInfo; + } + + update(other: KeymapInfo) { + this.layout = other.layout; + this.secondaryLayouts = other.secondaryLayouts; + this.mapping = other.mapping; + this.isUserKeyboardLayout = other.isUserKeyboardLayout; + this.layout.isUserKeyboardLayout = other.isUserKeyboardLayout; + } + + getScore(other: IRawMixedKeyboardMapping): number { + let score = 0; + for (let key in other) { + if (isWindows && (key === 'Backslash' || key === 'KeyQ')) { + // keymap from Chromium is probably wrong. + continue; + } + + if (isLinux && (key === 'Backspace' || key === 'Escape')) { + // native keymap doesn't align with keyboard event + continue; + } + + if (this.mapping[key] === undefined) { + score -= 1; + } + + let currentMapping = this.mapping[key]; + let otherMapping = other[key]; + + if (currentMapping.value !== otherMapping.value) { + score -= 1; + } + } + + return score; + } + + equal(other: KeymapInfo): boolean { + if (this.isUserKeyboardLayout !== other.isUserKeyboardLayout) { + return false; + } + + if (getKeyboardLayoutId(this.layout) !== getKeyboardLayoutId(other.layout)) { + return false; + } + + return this.fuzzyEqual(other.mapping); + } + + fuzzyEqual(other: IRawMixedKeyboardMapping): boolean { + for (let key in other) { + if (isWindows && (key === 'Backslash' || key === 'KeyQ')) { + // keymap from Chromium is probably wrong. + continue; + } + if (this.mapping[key] === undefined) { + return false; + } + + let currentMapping = this.mapping[key]; + let otherMapping = other[key]; + + if (currentMapping.value !== otherMapping.value) { + return false; + } + } + + return true; + } +} diff --git a/src/vs/workbench/services/keybinding/common/keymapService.ts b/src/vs/workbench/services/keybinding/common/keymapService.ts deleted file mode 100644 index e6014b6cccd..00000000000 --- a/src/vs/workbench/services/keybinding/common/keymapService.ts +++ /dev/null @@ -1,99 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { DispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; -import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; -import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; - -export interface IWindowsKeyMapping { - vkey: string; - value: string; - withShift: string; - withAltGr: string; - withShiftAltGr: string; -} -export interface IWindowsKeyboardMapping { - [code: string]: IWindowsKeyMapping; -} -export interface ILinuxKeyMapping { - value: string; - withShift: string; - withAltGr: string; - withShiftAltGr: string; -} -export interface ILinuxKeyboardMapping { - [code: string]: ILinuxKeyMapping; -} -export interface IMacKeyMapping { - value: string; - withShift: string; - withAltGr: string; - withShiftAltGr: string; - valueIsDeadKey: boolean; - withShiftIsDeadKey: boolean; - withAltGrIsDeadKey: boolean; - withShiftAltGrIsDeadKey: boolean; -} -export interface IMacKeyboardMapping { - [code: string]: IMacKeyMapping; -} - -export type IKeyboardMapping = IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping; - -/* __GDPR__FRAGMENT__ - "IKeyboardLayoutInfo" : { - "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } -*/ -export interface IWindowsKeyboardLayoutInfo { - name: string; - id: string; - text: string; -} - -/* __GDPR__FRAGMENT__ - "IKeyboardLayoutInfo" : { - "model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } -*/ -export interface ILinuxKeyboardLayoutInfo { - model: string; - layout: string; - variant: string; - options: string; - rules: string; -} - -/* __GDPR__FRAGMENT__ - "IKeyboardLayoutInfo" : { - "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } -*/ -export interface IMacKeyboardLayoutInfo { - id: string; - lang: string; -} - -export type IKeyboardLayoutInfo = IWindowsKeyboardLayoutInfo | ILinuxKeyboardLayoutInfo | IMacKeyboardLayoutInfo; - -export const IKeymapService = createDecorator('keymapService'); - -export interface IKeymapService { - _serviceBrand: ServiceIdentifier; - onDidChangeKeyboardMapper: Event; - getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper; - getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null; - getRawKeyboardMapping(): IKeyboardMapping | null; - validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void; -} diff --git a/src/vs/workbench/services/keybinding/common/navigatorKeyboard.ts b/src/vs/workbench/services/keybinding/common/navigatorKeyboard.ts new file mode 100644 index 00000000000..51e8de9f790 --- /dev/null +++ b/src/vs/workbench/services/keybinding/common/navigatorKeyboard.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface IKeyboard { + getLayoutMap(): Promise; + lock(keyCodes?: string[]): Promise; + unlock(): void; + addEventListener?(type: string, listener: () => void): void; + +} +export type INavigatorWithKeyboard = Navigator & { + keyboard: IKeyboard +}; \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts index 983ac53234e..475c881af9f 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts @@ -5,7 +5,7 @@ import * as nativeKeymap from 'native-keymap'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IKeymapService, IKeyboardLayoutInfo, IKeyboardMapping } from 'vs/workbench/services/keybinding/common/keymapService'; +import { IKeymapService, IKeyboardLayoutInfo, IKeyboardMapping } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IKeyboardMapper, CachedKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; import { Emitter, Event } from 'vs/base/common/event'; @@ -153,6 +153,10 @@ class NativeKeymapService extends Disposable implements IKeymapService { return KeyboardMapperFactory.INSTANCE.getCurrentKeyboardLayout(); } + getAllKeyboardLayouts(): IKeyboardLayoutInfo[] { + return []; + } + public getRawKeyboardMapping(): IKeyboardMapping | null { return KeyboardMapperFactory.INSTANCE.getRawKeyboardMapping(); } diff --git a/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts new file mode 100644 index 00000000000..8c06d950aed --- /dev/null +++ b/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import 'vs/workbench/services/keybinding/browser/keyboardLayouts/en.darwin'; // 15% +import 'vs/workbench/services/keybinding/browser/keyboardLayouts/de.darwin'; +import { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution'; +import { BrowserKeyboardMapperFactoryBase } from '../browser/keymapService'; +import { KeymapInfo, IKeymapInfo } from '../common/keymapInfo'; + +class TestKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBase { + constructor() { + super(); + + let keymapInfos: IKeymapInfo[] = KeyboardLayoutContribution.INSTANCE.layoutInfos; + this._keymapInfos.push(...keymapInfos.map(info => (new KeymapInfo(info.layout, info.secondaryLayouts, info.mapping, info.isUserKeyboardLayout)))); + this._mru = this._keymapInfos; + this._initialized = true; + this.onKeyboardLayoutChanged(); + } +} + + +suite('keyboard layout loader', () => { + const instance: TestKeyboardMapperFactory = new TestKeyboardMapperFactory(); + + test('load default US keyboard layout', () => { + assert.notEqual(instance.activeKeyboardLayout, null); + assert.equal(instance.activeKeyboardLayout!.isUSStandard, true); + }); + + test('isKeyMappingActive', () => { + assert.equal(instance.isKeyMappingActive({ + KeyA: { + value: 'a', + valueIsDeadKey: false, + withShift: 'A', + withShiftIsDeadKey: false, + withAltGr: 'å', + withAltGrIsDeadKey: false, + withShiftAltGr: 'Å', + withShiftAltGrIsDeadKey: false + } + }), true); + + assert.equal(instance.isKeyMappingActive({ + KeyA: { + value: 'a', + valueIsDeadKey: false, + withShift: 'A', + withShiftIsDeadKey: false, + withAltGr: 'å', + withAltGrIsDeadKey: false, + withShiftAltGr: 'Å', + withShiftAltGrIsDeadKey: false + }, + KeyZ: { + value: 'z', + valueIsDeadKey: false, + withShift: 'Z', + withShiftIsDeadKey: false, + withAltGr: 'Ω', + withAltGrIsDeadKey: false, + withShiftAltGr: '¸', + withShiftAltGrIsDeadKey: false + } + }), true); + + assert.equal(instance.isKeyMappingActive({ + KeyZ: { + value: 'y', + valueIsDeadKey: false, + withShift: 'Y', + withShiftIsDeadKey: false, + withAltGr: '¥', + withAltGrIsDeadKey: false, + withShiftAltGr: 'Ÿ', + withShiftAltGrIsDeadKey: false + }, + }), false); + + }); + + test('Switch keymapping', () => { + instance.setActiveKeyMapping({ + KeyZ: { + value: 'y', + valueIsDeadKey: false, + withShift: 'Y', + withShiftIsDeadKey: false, + withAltGr: '¥', + withAltGrIsDeadKey: false, + withShiftAltGr: 'Ÿ', + withShiftAltGrIsDeadKey: false + } + }); + assert.equal(!!instance.activeKeyboardLayout!.isUSStandard, false); + assert.equal(instance.isKeyMappingActive({ + KeyZ: { + value: 'y', + valueIsDeadKey: false, + withShift: 'Y', + withShiftIsDeadKey: false, + withAltGr: '¥', + withAltGrIsDeadKey: false, + withShiftAltGr: 'Ÿ', + withShiftAltGrIsDeadKey: false + }, + }), true); + + instance.setUSKeyboardLayout(); + assert.equal(instance.activeKeyboardLayout!.isUSStandard, true); + }); + + test('Switch keyboard layout info', () => { + instance.setKeyboardLayout('com.apple.keylayout.German'); + assert.equal(!!instance.activeKeyboardLayout!.isUSStandard, false); + assert.equal(instance.isKeyMappingActive({ + KeyZ: { + value: 'y', + valueIsDeadKey: false, + withShift: 'Y', + withShiftIsDeadKey: false, + withAltGr: '¥', + withAltGrIsDeadKey: false, + withShiftAltGr: 'Ÿ', + withShiftAltGrIsDeadKey: false + }, + }), true); + + instance.setUSKeyboardLayout(); + assert.equal(instance.activeKeyboardLayout!.isUSStandard, true); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index fe2da11d9cd..255ec9727fc 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -40,7 +40,7 @@ import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/comm import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestLogService, TestTextFileService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; +import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestTextFileService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; @@ -79,7 +79,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IEditorService, new TestEditorService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModeService, ModeServiceImpl); - instantiationService.stub(ILogService, new TestLogService()); + instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); const fileService = new FileService(new NullLogService()); diff --git a/src/vs/workbench/services/panel/common/panelService.ts b/src/vs/workbench/services/panel/common/panelService.ts index 501f6f8df8c..9e957d61747 100644 --- a/src/vs/workbench/services/panel/common/panelService.ts +++ b/src/vs/workbench/services/panel/common/panelService.ts @@ -8,7 +8,7 @@ import { IPanel } from 'vs/workbench/common/panel'; import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export const IPanelService = createDecorator('panelService'); @@ -53,7 +53,7 @@ export interface IPanelService { /** * Returns the progress indicator for the panel bar. */ - getProgressIndicator(id: string): ILocalProgressService | null; + getProgressIndicator(id: string): IProgressIndicator | null; /** * Show an activity in a panel. diff --git a/src/vs/workbench/services/progress/browser/editorProgressService.ts b/src/vs/workbench/services/progress/browser/editorProgressService.ts new file mode 100644 index 00000000000..7ea3daae18e --- /dev/null +++ b/src/vs/workbench/services/progress/browser/editorProgressService.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { ProgressBarIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; + +export class EditorProgressService extends ProgressBarIndicator { + + _serviceBrand: ServiceIdentifier; +} diff --git a/src/vs/workbench/services/progress/browser/localProgressService.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts similarity index 74% rename from src/vs/workbench/services/progress/browser/localProgressService.ts rename to src/vs/workbench/services/progress/browser/progressIndicator.ts index 2f2932873d7..1d11c084f7c 100644 --- a/src/vs/workbench/services/progress/browser/localProgressService.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -4,263 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import * as types from 'vs/base/common/types'; +import { isUndefinedOrNull } from 'vs/base/common/types'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ILocalProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; -import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IProgressRunner, IProgressIndicator } from 'vs/platform/progress/common/progress'; -namespace ProgressState { - - export const enum Type { - None, - Done, - Infinite, - While, - Work - } - - export const None = new class { readonly type = Type.None; }; - export const Done = new class { readonly type = Type.Done; }; - export const Infinite = new class { readonly type = Type.Infinite; }; - - export class While { - readonly type = Type.While; - - constructor( - readonly whilePromise: Promise, - readonly whileStart: number, - readonly whileDelay: number, - ) { } - } - - export class Work { - readonly type = Type.Work; - - constructor( - readonly total: number | undefined, - readonly worked: number | undefined - ) { } - } - - export type State = - typeof None - | typeof Done - | typeof Infinite - | While - | Work; -} - -export abstract class ScopedService extends Disposable { - - constructor( - private viewletService: IViewletService, - private panelService: IPanelService, - private scopeId: string - ) { - super(); - - this.registerListeners(); - } - - registerListeners(): void { - this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); - this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); - - this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId()))); - this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId()))); - } - - private onScopeClosed(scopeId: string) { - if (scopeId === this.scopeId) { - this.onScopeDeactivated(); - } - } - - private onScopeOpened(scopeId: string) { - if (scopeId === this.scopeId) { - this.onScopeActivated(); - } - } - - abstract onScopeActivated(): void; - - abstract onScopeDeactivated(): void; -} - -export class ScopedProgressService extends ScopedService implements ILocalProgressService { - - _serviceBrand: ServiceIdentifier; - - private isActive: boolean; - private progressbar: ProgressBar; - private progressState: ProgressState.State = ProgressState.None; - - constructor( - progressbar: ProgressBar, - scopeId: string, - isActive: boolean, - @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService - ) { - super(viewletService, panelService, scopeId); - - this.progressbar = progressbar; - this.isActive = isActive || types.isUndefinedOrNull(scopeId); // If service is unscoped, enable by default - } - - onScopeDeactivated(): void { - this.isActive = false; - } - - onScopeActivated(): void { - this.isActive = true; - - // Return early if progress state indicates that progress is done - if (this.progressState.type === ProgressState.Done.type) { - return; - } - - // Replay Infinite Progress from Promise - if (this.progressState.type === ProgressState.Type.While) { - let delay: number | undefined; - if (this.progressState.whileDelay > 0) { - const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart); - if (remainingDelay > 0) { - delay = remainingDelay; - } - } - - this.doShowWhile(delay); - } - - // Replay Infinite Progress - else if (this.progressState.type === ProgressState.Type.Infinite) { - this.progressbar.infinite().show(); - } - - // Replay Finite Progress (Total & Worked) - else if (this.progressState.type === ProgressState.Type.Work) { - if (this.progressState.total) { - this.progressbar.total(this.progressState.total).show(); - } - - if (this.progressState.worked) { - this.progressbar.worked(this.progressState.worked).show(); - } - } - } - - show(infinite: true, delay?: number): IProgressRunner; - show(total: number, delay?: number): IProgressRunner; - show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { - - // Sort out Arguments - if (typeof infiniteOrTotal === 'boolean') { - this.progressState = ProgressState.Infinite; - } else { - this.progressState = new ProgressState.Work(infiniteOrTotal, undefined); - } - - // Active: Show Progress - if (this.isActive) { - - // Infinite: Start Progressbar and Show after Delay - if (this.progressState.type === ProgressState.Type.Infinite) { - this.progressbar.infinite().show(delay); - } - - // Finite: Start Progressbar and Show after Delay - else if (this.progressState.type === ProgressState.Type.Work && typeof this.progressState.total === 'number') { - this.progressbar.total(this.progressState.total).show(delay); - } - } - - return { - total: (total: number) => { - this.progressState = new ProgressState.Work( - total, - this.progressState.type === ProgressState.Type.Work ? this.progressState.worked : undefined); - - if (this.isActive) { - this.progressbar.total(total); - } - }, - - worked: (worked: number) => { - - // Verify first that we are either not active or the progressbar has a total set - if (!this.isActive || this.progressbar.hasTotal()) { - this.progressState = new ProgressState.Work( - this.progressState.type === ProgressState.Type.Work ? this.progressState.total : undefined, - this.progressState.type === ProgressState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked); - - if (this.isActive) { - this.progressbar.worked(worked); - } - } - - // Otherwise the progress bar does not support worked(), we fallback to infinite() progress - else { - this.progressState = ProgressState.Infinite; - this.progressbar.infinite().show(); - } - }, - - done: () => { - this.progressState = ProgressState.Done; - - if (this.isActive) { - this.progressbar.stop().hide(); - } - } - }; - } - - async showWhile(promise: Promise, delay?: number): Promise { - - // Join with existing running promise to ensure progress is accurate - if (this.progressState.type === ProgressState.Type.While) { - promise = Promise.all([promise, this.progressState.whilePromise]); - } - - // Keep Promise in State - this.progressState = new ProgressState.While(promise, delay || 0, Date.now()); - - try { - this.doShowWhile(delay); - - await promise; - } catch (error) { - // ignore - } finally { - - // If this is not the last promise in the list of joined promises, skip this - if (this.progressState.type !== ProgressState.Type.While || this.progressState.whilePromise === promise) { - - // The while promise is either null or equal the promise we last hooked on - this.progressState = ProgressState.None; - - if (this.isActive) { - this.progressbar.stop().hide(); - } - } - } - } - - private doShowWhile(delay?: number): void { - - // Show Progress when active - if (this.isActive) { - this.progressbar.infinite().show(delay); - } - } -} - -export class LocalProgressService implements ILocalProgressService { - - _serviceBrand: ServiceIdentifier; +export class ProgressBarIndicator implements IProgressIndicator { constructor(private progressbar: ProgressBar) { } @@ -304,3 +54,247 @@ export class LocalProgressService implements ILocalProgressService { } } } + +namespace ProgressIndicatorState { + + export const enum Type { + None, + Done, + Infinite, + While, + Work + } + + export const None = new class { readonly type = Type.None; }; + export const Done = new class { readonly type = Type.Done; }; + export const Infinite = new class { readonly type = Type.Infinite; }; + + export class While { + readonly type = Type.While; + + constructor( + readonly whilePromise: Promise, + readonly whileStart: number, + readonly whileDelay: number, + ) { } + } + + export class Work { + readonly type = Type.Work; + + constructor( + readonly total: number | undefined, + readonly worked: number | undefined + ) { } + } + + export type State = + typeof None + | typeof Done + | typeof Infinite + | While + | Work; +} + +export abstract class CompositeScope extends Disposable { + + constructor( + private viewletService: IViewletService, + private panelService: IPanelService, + private scopeId: string + ) { + super(); + + this.registerListeners(); + } + + registerListeners(): void { + this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); + this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); + + this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId()))); + this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId()))); + } + + private onScopeClosed(scopeId: string) { + if (scopeId === this.scopeId) { + this.onScopeDeactivated(); + } + } + + private onScopeOpened(scopeId: string) { + if (scopeId === this.scopeId) { + this.onScopeActivated(); + } + } + + abstract onScopeActivated(): void; + + abstract onScopeDeactivated(): void; +} + +export class CompositeProgressIndicator extends CompositeScope implements IProgressIndicator { + private isActive: boolean; + private progressbar: ProgressBar; + private progressState: ProgressIndicatorState.State = ProgressIndicatorState.None; + + constructor( + progressbar: ProgressBar, + scopeId: string, + isActive: boolean, + @IViewletService viewletService: IViewletService, + @IPanelService panelService: IPanelService + ) { + super(viewletService, panelService, scopeId); + + this.progressbar = progressbar; + this.isActive = isActive || isUndefinedOrNull(scopeId); // If service is unscoped, enable by default + } + + onScopeDeactivated(): void { + this.isActive = false; + } + + onScopeActivated(): void { + this.isActive = true; + + // Return early if progress state indicates that progress is done + if (this.progressState.type === ProgressIndicatorState.Done.type) { + return; + } + + // Replay Infinite Progress from Promise + if (this.progressState.type === ProgressIndicatorState.Type.While) { + let delay: number | undefined; + if (this.progressState.whileDelay > 0) { + const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart); + if (remainingDelay > 0) { + delay = remainingDelay; + } + } + + this.doShowWhile(delay); + } + + // Replay Infinite Progress + else if (this.progressState.type === ProgressIndicatorState.Type.Infinite) { + this.progressbar.infinite().show(); + } + + // Replay Finite Progress (Total & Worked) + else if (this.progressState.type === ProgressIndicatorState.Type.Work) { + if (this.progressState.total) { + this.progressbar.total(this.progressState.total).show(); + } + + if (this.progressState.worked) { + this.progressbar.worked(this.progressState.worked).show(); + } + } + } + + show(infinite: true, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + + // Sort out Arguments + if (typeof infiniteOrTotal === 'boolean') { + this.progressState = ProgressIndicatorState.Infinite; + } else { + this.progressState = new ProgressIndicatorState.Work(infiniteOrTotal, undefined); + } + + // Active: Show Progress + if (this.isActive) { + + // Infinite: Start Progressbar and Show after Delay + if (this.progressState.type === ProgressIndicatorState.Type.Infinite) { + this.progressbar.infinite().show(delay); + } + + // Finite: Start Progressbar and Show after Delay + else if (this.progressState.type === ProgressIndicatorState.Type.Work && typeof this.progressState.total === 'number') { + this.progressbar.total(this.progressState.total).show(delay); + } + } + + return { + total: (total: number) => { + this.progressState = new ProgressIndicatorState.Work( + total, + this.progressState.type === ProgressIndicatorState.Type.Work ? this.progressState.worked : undefined); + + if (this.isActive) { + this.progressbar.total(total); + } + }, + + worked: (worked: number) => { + + // Verify first that we are either not active or the progressbar has a total set + if (!this.isActive || this.progressbar.hasTotal()) { + this.progressState = new ProgressIndicatorState.Work( + this.progressState.type === ProgressIndicatorState.Type.Work ? this.progressState.total : undefined, + this.progressState.type === ProgressIndicatorState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked); + + if (this.isActive) { + this.progressbar.worked(worked); + } + } + + // Otherwise the progress bar does not support worked(), we fallback to infinite() progress + else { + this.progressState = ProgressIndicatorState.Infinite; + this.progressbar.infinite().show(); + } + }, + + done: () => { + this.progressState = ProgressIndicatorState.Done; + + if (this.isActive) { + this.progressbar.stop().hide(); + } + } + }; + } + + async showWhile(promise: Promise, delay?: number): Promise { + + // Join with existing running promise to ensure progress is accurate + if (this.progressState.type === ProgressIndicatorState.Type.While) { + promise = Promise.all([promise, this.progressState.whilePromise]); + } + + // Keep Promise in State + this.progressState = new ProgressIndicatorState.While(promise, delay || 0, Date.now()); + + try { + this.doShowWhile(delay); + + await promise; + } catch (error) { + // ignore + } finally { + + // If this is not the last promise in the list of joined promises, skip this + if (this.progressState.type !== ProgressIndicatorState.Type.While || this.progressState.whilePromise === promise) { + + // The while promise is either null or equal the promise we last hooked on + this.progressState = ProgressIndicatorState.None; + + if (this.isActive) { + this.progressbar.stop().hide(); + } + } + } + } + + private doShowWhile(delay?: number): void { + + // Show Progress when active + if (this.isActive) { + this.progressbar.infinite().show(delay); + } + } +} diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 852faf28a93..d7bb38745a1 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/progressService'; import { localize } from 'vs/nls'; import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { timeout } from 'vs/base/common/async'; @@ -286,7 +286,7 @@ export class ProgressService extends Disposable implements IProgressService { return this.withCompositeProgress(this.panelService.getProgressIndicator(panelid), task, options); } - private withCompositeProgress

, R = unknown>(compositeProgressService: ILocalProgressService | null, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { + private withCompositeProgress

, R = unknown>(progressIndicator: IProgressIndicator | null, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { let progressRunner: IProgressRunner | undefined = undefined; const promise = task({ @@ -305,12 +305,12 @@ export class ProgressService extends Disposable implements IProgressService { } }); - if (compositeProgressService) { + if (progressIndicator) { if (typeof options.total === 'number') { - progressRunner = compositeProgressService.show(options.total, options.delay); + progressRunner = progressIndicator.show(options.total, options.delay); promise.catch(() => undefined /* ignore */).finally(() => progressRunner ? progressRunner.done() : undefined); } else { - compositeProgressService.showWhile(promise, options.delay); + progressIndicator.showWhile(promise, options.delay); } } diff --git a/src/vs/workbench/services/progress/test/localProgressService.test.ts b/src/vs/workbench/services/progress/test/progressIndicator.test.ts similarity index 66% rename from src/vs/workbench/services/progress/test/localProgressService.test.ts rename to src/vs/workbench/services/progress/test/progressIndicator.test.ts index dad585ef4f0..a8d2b740d0b 100644 --- a/src/vs/workbench/services/progress/test/localProgressService.test.ts +++ b/src/vs/workbench/services/progress/test/progressIndicator.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { IEditorControl } from 'vs/workbench/common/editor'; -import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/localProgressService'; +import { CompositeScope, CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; @@ -16,106 +16,55 @@ class TestViewlet implements IViewlet { constructor(private id: string) { } - getId(): string { - return this.id; - } - - /** - * Returns the name of this composite to show in the title area. - */ - getTitle(): string { - return this.id; - } - - /** - * Returns the primary actions of the composite. - */ - getActions(): IAction[] { - return []; - } - - /** - * Returns the secondary actions of the composite. - */ - getSecondaryActions(): IAction[] { - return []; - } - - /** - * Returns an array of actions to show in the context menu of the composite - */ - public getContextMenuActions(): IAction[] { - return []; - } - - /** - * Returns the action item for a specific action. - */ - getActionViewItem(action: IAction): IActionViewItem { - return null!; - } - - /** - * Returns the underlying control of this composite. - */ - getControl(): IEditorControl { - return null!; - } - - /** - * Asks the underlying control to focus. - */ - focus(): void { - } - - getOptimalWidth(): number { - return 10; - } + getId(): string { return this.id; } + getTitle(): string { return this.id; } + getActions(): IAction[] { return []; } + getSecondaryActions(): IAction[] { return []; } + getContextMenuActions(): IAction[] { return []; } + getActionViewItem(action: IAction): IActionViewItem { return null!; } + getControl(): IEditorControl { return null!; } + focus(): void { } + getOptimalWidth(): number { return 10; } } -class TestScopedService extends ScopedService { - public isActive: boolean; +class TestCompositeScope extends CompositeScope { + isActive: boolean; constructor(viewletService: IViewletService, panelService: IPanelService, scopeId: string) { super(viewletService, panelService, scopeId); } - public onScopeActivated() { - this.isActive = true; - } - public onScopeDeactivated() { - this.isActive = false; - } + onScopeActivated() { this.isActive = true; } + onScopeDeactivated() { this.isActive = false; } } class TestProgressBar { - public fTotal: number; - public fWorked: number; - public fInfinite: boolean; - public fDone: boolean; + fTotal: number; + fWorked: number; + fInfinite: boolean; + fDone: boolean; - constructor() { - } + constructor() { } - public infinite() { + infinite() { this.fDone = null!; this.fInfinite = true; return this; } - public total(total: number) { + total(total: number) { this.fDone = null!; this.fTotal = total; return this; } - public hasTotal() { + hasTotal() { return !!this.fTotal; } - public worked(worked: number) { + worked(worked: number) { this.fDone = null!; if (this.fWorked) { @@ -127,7 +76,7 @@ class TestProgressBar { return this; } - public done() { + done() { this.fDone = true; this.fInfinite = null!; @@ -137,25 +86,21 @@ class TestProgressBar { return this; } - public stop() { + stop() { return this.done(); } - public show(): void { + show(): void { } - } - - public hide(): void { - - } + hide(): void { } } -suite('Progress Service', () => { +suite('Progress Indicator', () => { - test('ScopedService', () => { + test('CompositeScope', () => { let viewletService = new TestViewletService(); let panelService = new TestPanelService(); - let service = new TestScopedService(viewletService, panelService, 'test.scopeId'); + let service = new TestCompositeScope(viewletService, panelService, 'test.scopeId'); const testViewlet = new TestViewlet('test.scopeId'); assert(!service.isActive); @@ -167,11 +112,11 @@ suite('Progress Service', () => { }); - test('WorkbenchProgressService', async () => { + test('CompositeProgressIndicator', async () => { let testProgressBar = new TestProgressBar(); let viewletService = new TestViewletService(); let panelService = new TestPanelService(); - let service = new ScopedProgressService((testProgressBar), 'test.scopeId', true, viewletService, panelService); + let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, viewletService, panelService); // Active: Show (Infinite) let fn = service.show(true); diff --git a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts index 3f2d25705ad..ee8b3c9b9d2 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService'; @@ -16,14 +16,14 @@ export class RemoteAgentService extends AbstractRemoteAgentService { private readonly _connection: IRemoteAgentConnection | null = null; constructor( - @IEnvironmentService environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService signService: ISignService ) { super(environmentService); - const authority = document.location.host; - this._connection = this._register(new RemoteAgentConnection(authority, productService.commit, browserWebSocketFactory, environmentService, remoteAuthorityResolverService, signService)); + + this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.commit, browserWebSocketFactory, environmentService, remoteAuthorityResolverService, signService)); } getConnection(): IRemoteAgentConnection | null { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 7051709d15f..8c793ca7a87 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -20,6 +20,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, isFileMatch, isProgressMessage } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class SearchService extends Disposable implements ISearchService { _serviceBrand: any; @@ -421,3 +422,4 @@ export class RemoteSearchService extends SearchService { } } +registerSingleton(ISearchService, RemoteSearchService, true); diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index c445313f091..5583a243cb2 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -16,6 +16,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties'; import { TelemetryService as BaseTelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; export class TelemetryService extends Disposable implements ITelemetryService { @@ -59,6 +60,10 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.impl.publicLog(eventName, data, anonymizeFilePaths); } + publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean) { + return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths); + } + getTelemetryInfo(): Promise { return this.impl.getTelemetryInfo(); } diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index 316a6f97a50..e25b10d9696 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -462,6 +462,11 @@ class TMTokenization implements ITokenizationSupport { this._containsEmbeddedLanguages = containsEmbeddedLanguages; this._seenLanguages = []; this._maxTokenizationLineLength = configurationService.getValue('editor.maxTokenizationLineLength'); + configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.maxTokenizationLineLength')) { + this._maxTokenizationLineLength = configurationService.getValue('editor.maxTokenizationLineLength'); + } + }); } public getInitialState(): IState { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index d15549e21cc..8d653165119 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -6,6 +6,7 @@ import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; import { ITextFileService, IResourceEncodings, IResourceEncoding } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; export class BrowserTextFileService extends TextFileService { @@ -14,6 +15,19 @@ export class BrowserTextFileService extends TextFileService { return { encoding: 'utf8', hasBOM: false }; } }; + + protected beforeShutdown(reason: ShutdownReason): boolean | Promise { + const veto = super.beforeShutdown(reason); + + // Web: there is no support for long running unload handlers. As such + // we need to return a direct boolean veto when we detect that there + // are dirty files around. + if (veto instanceof Promise) { + return this.getDirty().length > 0; + } + + return veto; + } } registerSingleton(ITextFileService, BrowserTextFileService); \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 4c092ee588b..bb58fa2e153 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -119,7 +119,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer })); } - private beforeShutdown(reason: ShutdownReason): boolean | Promise { + protected beforeShutdown(reason: ShutdownReason): boolean | Promise { // Dirty files need treatment on shutdown const dirty = this.getDirty(); @@ -648,18 +648,19 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return result; } - protected async promptForPath(resource: URI, defaultUri: URI): Promise { + protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { // Help user to find a name for the file by opening it first await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true, } }); - return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri)); + return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); } - private getSaveDialogOptions(defaultUri: URI): ISaveDialogOptions { + private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { const options: ISaveDialogOptions = { defaultUri, - title: nls.localize('saveAsTitle', "Save As") + title: nls.localize('saveAsTitle', "Save As"), + availableFileSystems, }; // Filters are only enabled on Windows where they work properly @@ -765,7 +766,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer dialogPath = this.suggestFileName(resource); } - targetResource = await this.promptForPath(resource, dialogPath); + targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined); } if (!targetResource) { @@ -855,14 +856,15 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return false; } - // take over encoding, mode and model value from source model + // take over encoding, mode (only if more specific) and model value from source model targetModel.updatePreferredEncoding(sourceModel.getEncoding()); if (sourceModel.isResolved() && targetModel.isResolved()) { this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); - const mode = sourceModel.textEditorModel.getLanguageIdentifier(); - if (mode.language !== PLAINTEXT_MODE_ID) { - targetModel.textEditorModel.setMode(mode); // only use if more specific than plain/text + const sourceMode = sourceModel.textEditorModel.getLanguageIdentifier(); + const targetMode = targetModel.textEditorModel.getLanguageIdentifier(); + if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { + targetModel.textEditorModel.setMode(sourceMode); // only use if more specific than plain/text } } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index d2178242607..a83474a5806 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -428,6 +428,7 @@ export interface ISaveOptions { overwriteEncoding?: boolean; skipSaveParticipants?: boolean; writeElevated?: boolean; + availableFileSystems?: string[]; } export interface ILoadOptions { diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index a20befa6940..92349b87a4b 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -7,7 +7,7 @@ import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export const IViewletService = createDecorator('viewletService'); @@ -48,7 +48,7 @@ export interface IViewletService { /** * Returns the progress indicator for the side bar. */ - getProgressIndicator(id: string): ILocalProgressService | null; + getProgressIndicator(id: string): IProgressIndicator | null; /** * Hide the active viewlet. diff --git a/src/vs/workbench/services/window/electron-browser/windowService.ts b/src/vs/workbench/services/window/electron-browser/windowService.ts index d3975f0acdd..52a00f05b26 100644 --- a/src/vs/workbench/services/window/electron-browser/windowService.ts +++ b/src/vs/workbench/services/window/electron-browser/windowService.ts @@ -109,7 +109,7 @@ export class WindowService extends Disposable implements IWindowService { return this.windowsService.closeWindow(this.windowId); } - toggleFullScreen(): Promise { + toggleFullScreen(target?: HTMLElement): Promise { return this.windowsService.toggleFullScreen(this.windowId); } diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts index d0c60e71c6a..905d2a8b292 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -57,14 +57,16 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @ILifecycleService readonly lifecycleService: ILifecycleService, @ILabelService readonly labelService: ILabelService ) { + this.registerListeners(); + } - lifecycleService.onBeforeShutdown(async e => { + private registerListeners(): void { + this.lifecycleService.onBeforeShutdown(async e => { const saveOperation = this.saveUntitedBeforeShutdown(e.reason); if (saveOperation) { e.veto(saveOperation); } }); - } private async saveUntitedBeforeShutdown(reason: ShutdownReason): Promise { diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index 047d78070f3..8eb327c11c7 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -796,9 +796,9 @@ suite('ExtHostLanguageFeatureCommands', function () { })); await rpcProtocol.sync(); - let value = await commands.executeCommand('vscode.executeSelectionRangeProvider', model.uri, [new types.Position(0, 10)]); + let value = await commands.executeCommand('vscode.executeSelectionRangeProvider', model.uri, [new types.Position(0, 10)]); assert.equal(value.length, 1); - assert.ok(value[0].length >= 2); + assert.ok(value[0].parent); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index f7d746d4761..0d15201d893 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -16,8 +16,8 @@ import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; -import { TestLogService } from 'vs/workbench/test/workbenchTestServices'; import * as vscode from 'vscode'; +import { NullLogService } from 'vs/platform/log/common/log'; let rpcProtocol: TestRPCProtocol; let extHostSearch: ExtHostSearch; @@ -130,7 +130,7 @@ suite('ExtHostSearch', () => { rpcProtocol = new TestRPCProtocol(); mockMainThreadSearch = new MockMainThreadSearch(); - const logService = new TestLogService(); + const logService = new NullLogService(); rpcProtocol.set(MainContext.MainThreadSearch, mockMainThreadSearch); diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index a0c7e9a27fe..4b246719249 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -29,6 +29,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { TestContextService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; namespace Timer { export interface ITimerEvent { @@ -172,6 +173,10 @@ class TestTelemetryService implements ITelemetryService { return Promise.resolve(undefined); } + public publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as any); + } + public getTelemetryInfo(): Promise { return Promise.resolve({ instanceId: 'someValue.instanceId', diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index be16e39397a..e08748c6364 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -33,6 +33,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; declare var __dirname: string; @@ -165,6 +166,10 @@ class TestTelemetryService implements ITelemetryService { return Promise.resolve(); } + public publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as any); + } + public getTelemetryInfo(): Promise { return Promise.resolve({ instanceId: 'someValue.instanceId', diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index c33ab89e631..45bf09e967a 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -38,7 +38,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { IWindowsService, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, MenuBarVisibility, IURIToOpen, IOpenSettings, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -64,14 +64,14 @@ import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { Dimension } from 'vs/base/browser/dom'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; import { timeout } from 'vs/base/common/async'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; -import { isLinux, isMacintosh } from 'vs/base/common/platform'; +import { isLinux, isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; @@ -82,7 +82,7 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; -import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; +import { NodeTextFileService } from 'vs/workbench/services/textfile/node/textFileService'; import { Schemas } from 'vs/base/common/network'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { @@ -177,7 +177,7 @@ export class TestContextService implements IWorkspaceContextService { } } -export class TestTextFileService extends BrowserTextFileService { +export class TestTextFileService extends NodeTextFileService { public cleanupBackupsBeforeShutdownCalled: boolean; private promptPath: URI; @@ -311,7 +311,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IThemeService, new TestThemeService()); - instantiationService.stub(ILogService, new TestLogService()); + instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService([new TestEditorGroup(0)])); instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); const editorService = new TestEditorService(); @@ -322,19 +322,6 @@ export function workbenchInstantiationService(): IInstantiationService { return instantiationService; } -export class TestLogService implements ILogService { - _serviceBrand: any; onDidChangeLogLevel: Event; - getLevel(): LogLevel { return LogLevel.Info; } - setLevel(_level: LogLevel): void { } - trace(_message: string, ..._args: any[]): void { } - debug(_message: string, ..._args: any[]): void { } - info(_message: string, ..._args: any[]): void { } - warn(_message: string, ..._args: any[]): void { } - error(_message: string | Error, ..._args: any[]): void { } - critical(_message: string | Error, ..._args: any[]): void { } - dispose(): void { } -} - export class TestDecorationsService implements IDecorationsService { _serviceBrand: any; onDidChangeDecorations: Event = Event.None; @@ -1460,6 +1447,10 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(); } + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + return Promise.resolve(); + } + getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { throw new Error('not implemented'); } diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 412d58fbb2e..8535066a63b 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -20,6 +20,8 @@ import 'vs/workbench/electron-browser/main'; //#region --- workbench actions import 'vs/workbench/browser/actions/layoutActions'; +import 'vs/workbench/browser/actions/windowActions'; +import 'vs/workbench/browser/actions/developerActions'; import 'vs/workbench/browser/actions/listCommands'; import 'vs/workbench/browser/actions/navigationActions'; import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; @@ -89,8 +91,6 @@ import { IURLService } from 'vs/platform/url/common/url'; import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/workbench/services/remote/node/tunnelService'; -import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; @@ -135,6 +135,7 @@ import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/window/electron-browser/windowService'; import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; +import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; registerSingleton(IMenuService, MenuService, true); registerSingleton(IListService, ListService, true); @@ -162,7 +163,6 @@ registerSingleton(IWorkspacesService, WorkspacesService); registerSingleton(IMenubarService, MenubarService); registerSingleton(IURLService, RelayURLService); registerSingleton(ITunnelService, TunnelService, true); -registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); registerSingleton(ICredentialsService, KeytarCredentialsService, true); //#endregion @@ -202,6 +202,7 @@ registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); // Logs import 'vs/workbench/contrib/logs/common/logs.contribution'; +import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; // Quick Open Handlers import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; @@ -215,7 +216,7 @@ import 'vs/workbench/contrib/files/browser/files.contribution'; import 'vs/workbench/contrib/backup/common/backup.contribution'; // Stats -import 'vs/workbench/contrib/stats/node/stats.contribution'; +import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; // Rapid Render Splash import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; @@ -275,6 +276,7 @@ import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; registerSingleton(ITaskService, TaskService, true); // Remote +import 'vs/workbench/contrib/remote/common/remote.contribution'; import 'vs/workbench/contrib/remote/electron-browser/remote.contribution'; // Emmet diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 3085d6a4875..af41878783d 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -6,26 +6,57 @@ import 'vs/workbench/workbench.web.main'; import { main } from 'vs/workbench/browser/web.main'; import { UriComponents } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; export interface IWorkbenchConstructionOptions { + + /** + * Experimental: the remote authority is the IP:PORT from where the workbench is served + * from. It is for example being used for the websocket connections as address. + */ remoteAuthority: string; - userDataUri: UriComponents; - + /** + * Experimental: An endpoint to serve iframe content ("webview") from. This is required + * to provide full security isolation from the workbench host. + */ webviewEndpoint?: string; + /** + * Experimental: An optional folder that is set as workspace context for the workbench. + */ folderUri?: UriComponents; + + /** + * Experimental: An optional workspace that is set as workspace context for the workbench. + */ workspaceUri?: UriComponents; + + /** + * Experimental: The userData namespace is used to handle user specific application + * data like settings, keybindings, UI state and snippets. + */ + userDataProvider?: { + readonly onDidChangeFile: Event; + + readFile(path: string): Promise; + writeFile(path: string, content: Uint8Array): Promise; + deleteFile(path: string): Promise; + + listFiles(path: string): Promise; + }; } +/** + * Experimental: Creates the workbench with the provided options in the provided container. + * + * @param domElement the container to create the workbench in + * @param options for setting up the workbench + */ function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { return main(domElement, options); } -const api: any = self; - -api.monaco = { - workbench: { - create - } +export { + create }; \ No newline at end of file diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 09dc32edf03..c28adc0ad98 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -9,7 +9,6 @@ import 'vs/editor/editor.all'; import 'vs/workbench/api/browser/extensionHost.contribution'; -// import 'vs/workbench/electron-browser/main.contribution'; import 'vs/workbench/browser/workbench.contribution'; import 'vs/workbench/browser/web.main'; @@ -20,6 +19,8 @@ import 'vs/workbench/browser/web.main'; //#region --- workbench actions import 'vs/workbench/browser/actions/layoutActions'; +import 'vs/workbench/browser/actions/windowActions'; +import 'vs/workbench/browser/actions/developerActions'; import 'vs/workbench/browser/actions/listCommands'; import 'vs/workbench/browser/actions/navigationActions'; import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; @@ -62,14 +63,15 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; -import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; +// import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; // import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; // import { IRequestService } from 'vs/platform/request/node/request'; // import { RequestService } from 'vs/platform/request/electron-browser/requestService'; -// import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; -// import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { BrowserLifecycleService } from 'vs/platform/lifecycle/browser/lifecycleService'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { DialogService } from 'vs/platform/dialogs/browser/dialogService'; // import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; // import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; // import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; @@ -89,11 +91,8 @@ import { ContextViewService } from 'vs/platform/contextview/browser/contextViewS // import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; // import { ITunnelService } from 'vs/platform/remote/common/tunnel'; // import { TunnelService } from 'vs/workbench/services/remote/node/tunnelService'; -import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { ISearchService } from 'vs/workbench/services/search/common/search'; -import { RemoteSearchService } from 'vs/workbench/services/search/common/searchService'; -import 'vs/platform/dialogs/browser/dialogService'; +// import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +// import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; // import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; @@ -101,7 +100,7 @@ import 'vs/workbench/services/textMate/browser/textMateService'; // import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; // import 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; import 'vs/workbench/services/decorations/browser/decorationsService'; -// import 'vs/workbench/services/search/node/searchService'; +import 'vs/workbench/services/search/common/searchService'; import 'vs/workbench/services/progress/browser/progressService'; import 'vs/workbench/services/editor/browser/codeEditorService'; // import 'vs/workbench/services/extensions/electron-browser/extensionHostDebugService'; @@ -117,7 +116,7 @@ import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/browser/parts/views/views'; -import 'vs/workbench/services/keybinding/browser/keyboardLayoutService'; +import 'vs/workbench/services/keybinding/browser/keymapService'; import 'vs/workbench/services/keybinding/browser/keybindingService'; import 'vs/workbench/services/untitled/common/untitledEditorService'; import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; @@ -134,9 +133,13 @@ import 'vs/workbench/services/label/common/labelService'; import 'vs/workbench/services/notification/common/notificationService'; // import 'vs/workbench/services/window/electron-browser/windowService'; // import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; +import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import 'vs/workbench/browser/web.simpleservices'; +registerSingleton(IDialogService, DialogService, true); registerSingleton(IMenuService, MenuService, true); registerSingleton(IListService, ListService, true); registerSingleton(IOpenerService, OpenerService, true); @@ -152,19 +155,18 @@ registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); registerSingleton(IContextViewService, ContextViewService, true); // registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); // registerSingleton(IRequestService, RequestService, true); -// registerSingleton(ILifecycleService, LifecycleService); +registerSingleton(ILifecycleService, BrowserLifecycleService); // registerSingleton(ILocalizationsService, LocalizationsService); // registerSingleton(ISharedProcessService, SharedProcessService, true); -// registerSingleton(IProductService, ProductService, true); // registerSingleton(IWindowsService, WindowsService); // registerSingleton(IUpdateService, UpdateService); // registerSingleton(IIssueService, IssueService); // registerSingleton(IWorkspacesService, WorkspacesService); // registerSingleton(IMenubarService, MenubarService); // registerSingleton(IURLService, RelayURLService); -registerSingleton(ISearchService, RemoteSearchService, true); +// registerSingleton(ITunnelService, TunnelService, true); +// registerSingleton(ICredentialsService, KeytarCredentialsService, true); registerSingleton(IContextMenuService, ContextMenuService); -registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); //#endregion @@ -194,12 +196,10 @@ import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; // Localizations // import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; -// Labels -import 'vs/workbench/contrib/label/common/label.contribution'; - // Preferences import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; +import 'vs/workbench/contrib/preferences/browser/keyboardLayoutPicker'; import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); @@ -241,7 +241,6 @@ import 'vs/workbench/contrib/debug/browser/repl'; import 'vs/workbench/contrib/debug/browser/debugViewlet'; import 'vs/workbench/contrib/debug/browser/debugHelperService'; - // Markers import 'vs/workbench/contrib/markers/browser/markers.contribution'; @@ -291,8 +290,8 @@ import { TaskService } from 'vs/workbench/contrib/tasks/browser/taskService'; import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; registerSingleton(ITaskService, TaskService, true); - // Remote +import 'vs/workbench/contrib/remote/common/remote.contribution'; // import 'vs/workbench/contrib/remote/electron-browser/remote.contribution'; // Emmet diff --git a/test/electron/index.js b/test/electron/index.js index ebd97e3b98b..b0d82f6d1bd 100644 --- a/test/electron/index.js +++ b/test/electron/index.js @@ -100,6 +100,13 @@ function parseReporterOption(value) { app.on('ready', () => { + ipcMain.on('error', (_, err) => { + if (!argv.debug) { + console.error(err); + app.exit(1); + } + }); + const win = new BrowserWindow({ height: 600, width: 800, diff --git a/test/electron/renderer.js b/test/electron/renderer.js index addb1f0b93c..bd8cba69214 100644 --- a/test/electron/renderer.js +++ b/test/electron/renderer.js @@ -273,5 +273,12 @@ function runTests(opts) { ipcRenderer.on('run', (e, opts) => { initLoader(opts); - runTests(opts).catch(err => console.error(typeof err === 'string' ? err : JSON.stringify(err))); + runTests(opts).catch(err => { + if (!(typeof err !== 'string')) { + err = JSON.stringify(err); + } + + console.error(err); + ipcRenderer.send('error', err); + }); }); diff --git a/test/smoke/package.json b/test/smoke/package.json index 2aff1bced09..80f93f04d89 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -22,7 +22,7 @@ "@types/webdriverio": "4.6.1", "concurrently": "^3.5.1", "cpx": "^1.5.0", - "electron": "4.2.4", + "electron": "4.2.5", "htmlparser2": "^3.9.2", "mkdirp": "^0.5.1", "mocha": "^5.2.0", diff --git a/test/smoke/src/application.ts b/test/smoke/src/application.ts index 5d764176846..472d081ec4b 100644 --- a/test/smoke/src/application.ts +++ b/test/smoke/src/application.ts @@ -140,7 +140,7 @@ export class Application { await this.code.waitForElement('.monaco-workbench'); if (this.remote) { - await this.code.waitForElement('.monaco-workbench .statusbar-item.statusbar-entry a[title="Editing on TestResolver"]'); + await this.code.waitForElement('.monaco-workbench .statusbar-item[title="Editing on TestResolver"]'); } // wait a bit, since focus might be stolen off widgets diff --git a/test/smoke/src/areas/git/git.test.ts b/test/smoke/src/areas/git/git.test.ts index d04677eafbc..11546f7d7fe 100644 --- a/test/smoke/src/areas/git/git.test.ts +++ b/test/smoke/src/areas/git/git.test.ts @@ -7,7 +7,7 @@ import * as cp from 'child_process'; import { Application } from '../../application'; const DIFF_EDITOR_LINE_INSERT = '.monaco-diff-editor .editor.modified .line-insert'; -const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-entry a[title$="Synchronize Changes"]'; +const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-item[title$="Synchronize Changes"]'; export function setup() { describe('Git', () => { diff --git a/test/smoke/src/areas/statusbar/statusbar.ts b/test/smoke/src/areas/statusbar/statusbar.ts index c9bfa19bc5c..66e1186fd4c 100644 --- a/test/smoke/src/areas/statusbar/statusbar.ts +++ b/test/smoke/src/areas/statusbar/statusbar.ts @@ -38,7 +38,7 @@ export class StatusBar { } async waitForStatusbarText(title: string, text: string): Promise { - await this.code.waitForTextContent(`${this.mainSelector} span[title="${title}"]`, text); + await this.code.waitForTextContent(`${this.mainSelector} .statusbar-item[title="${title}"]`, text); } private getSelector(element: StatusBarElement): string { @@ -50,15 +50,15 @@ export class StatusBar { case StatusBarElement.PROBLEMS_STATUS: return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-error`; case StatusBarElement.SELECTION_STATUS: - return `${this.mainSelector} ${this.rightSelector} a[title="Go to Line"]`; + return `${this.mainSelector} ${this.rightSelector}[title="Go to Line"]`; case StatusBarElement.INDENTATION_STATUS: - return `${this.mainSelector} ${this.rightSelector} a[title="Select Indentation"]`; + return `${this.mainSelector} ${this.rightSelector}[title="Select Indentation"]`; case StatusBarElement.ENCODING_STATUS: - return `${this.mainSelector} ${this.rightSelector} a[title="Select Encoding"]`; + return `${this.mainSelector} ${this.rightSelector}[title="Select Encoding"]`; case StatusBarElement.EOL_STATUS: - return `${this.mainSelector} ${this.rightSelector} a[title="Select End of Line Sequence"]`; + return `${this.mainSelector} ${this.rightSelector}[title="Select End of Line Sequence"]`; case StatusBarElement.LANGUAGE_STATUS: - return `${this.mainSelector} ${this.rightSelector} a[title="Select Language Mode"]`; + return `${this.mainSelector} ${this.rightSelector}[title="Select Language Mode"]`; case StatusBarElement.FEEDBACK_ICON: return `${this.mainSelector} ${this.rightSelector} .monaco-dropdown.send-feedback`; default: diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index da2b5b77ed5..b6424c799b9 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -596,10 +596,10 @@ electron-download@^4.1.0: semver "^5.4.1" sumchecker "^2.0.2" -electron@4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/electron/-/electron-4.2.4.tgz#68ca7bd4ff2c16b9205549322f0f1cda4ebc9ff9" - integrity sha512-d4wEwJluMsRyRgbukLmFVTb6l1J+mc3RLB1ctbpMlSWDFvs+zknPWa+cHBzTWwrdgwINLddr69qsAW1ku6FqYw== +electron@4.2.5: + version "4.2.5" + resolved "https://registry.yarnpkg.com/electron/-/electron-4.2.5.tgz#1d1432c38e2b2190318f7ca30897cdfdcf942e5a" + integrity sha512-P132MXzTtyn2ZaekhKi5JeHzmTAMuR/uQt4hrg3vfJV7fpncx9SL6UFwHAK1DU13iiyZJqqIziNUu+o8nODHsA== dependencies: "@types/node" "^10.12.18" electron-download "^4.1.0" diff --git a/tslint.json b/tslint.json index 844f1999999..bf277c33bd1 100644 --- a/tslint.json +++ b/tslint.json @@ -441,6 +441,7 @@ "**/vs/base/**/{common,browser}/**", "**/vs/platform/**/{common,browser}/**", "**/vs/editor/{common,browser}/**", + "**/vs/workbench/workbench.web.api", "**/vs/workbench/{common,browser}/**", "**/vs/workbench/services/**/{common,browser}/**", "vscode-textmate", diff --git a/yarn.lock b/yarn.lock index 73658789903..acd186d7f7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -303,10 +303,10 @@ acorn@^5.0.0, acorn@^5.6.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" integrity sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ== -acorn@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7" - integrity sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w== +acorn@^5.5.0: + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== acorn@^6.0.2: version "6.0.7" @@ -327,24 +327,16 @@ agent-base@~4.2.0: dependencies: es6-promisify "^5.0.0" -ajv-keywords@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" - integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw= +ajv-keywords@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" + integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= ajv-keywords@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= -ajv@^4.7.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - ajv@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda" @@ -355,7 +347,7 @@ ajv@^5.1.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^5.3.0: +ajv@^5.2.3, ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= @@ -423,11 +415,6 @@ ansi-cyan@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-escapes@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" - integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= - ansi-escapes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" @@ -828,7 +815,7 @@ azure-storage@^2.10.2: xml2js "0.2.8" xmlbuilder "^9.0.7" -babel-code-frame@^6.16.0: +babel-code-frame@^6.22.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= @@ -1309,7 +1296,7 @@ chalk@2.3.1: escape-string-regexp "^1.0.5" supports-color "^5.2.0" -chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -1347,6 +1334,11 @@ chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +chardet@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= + chardet@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029" @@ -1473,13 +1465,6 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -cli-cursor@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" - integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= - dependencies: - restore-cursor "^1.0.1" - cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -1720,7 +1705,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.0, concat-stream@^1.5.2: +concat-stream@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" integrity sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc= @@ -1894,7 +1879,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@^5.0.1: +cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= @@ -2083,7 +2068,7 @@ debug@2.2.0: dependencies: ms "0.7.1" -debug@2.6.9, debug@^2.1.1, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2319,14 +2304,6 @@ dir-glob@^2.0.0: arrify "^1.0.1" path-type "^3.0.0" -doctrine@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" - integrity sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM= - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -2592,18 +2569,6 @@ es6-iterator@^2.0.1, es6-iterator@~2.0.1: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-map@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" - integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-set "~0.1.5" - es6-symbol "~3.1.1" - event-emitter "~0.3.5" - es6-promise@^4.0.3: version "4.2.4" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" @@ -2616,18 +2581,7 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -es6-set@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" - integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-symbol "3.1.1" - event-emitter "~0.3.5" - -es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: +es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= @@ -2684,13 +2638,11 @@ escodegen@1.8.x: optionalDependencies: source-map "~0.2.0" -escope@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" - integrity sha1-4Bl16BJ4GhY6ba392AOY3GTIicM= +eslint-scope@^3.7.1: + version "3.7.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" + integrity sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA== dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" esrecurse "^4.1.0" estraverse "^4.1.1" @@ -2712,46 +2664,49 @@ eslint-visitor-keys@^1.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== -eslint@^3.4.0: - version "3.19.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc" - integrity sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw= +eslint@^4.18.2: + version "4.19.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" + integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ== dependencies: - babel-code-frame "^6.16.0" - chalk "^1.1.3" - concat-stream "^1.5.2" - debug "^2.1.1" - doctrine "^2.0.0" - escope "^3.6.0" - espree "^3.4.0" + ajv "^5.3.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.1.0" + doctrine "^2.1.0" + eslint-scope "^3.7.1" + eslint-visitor-keys "^1.0.0" + espree "^3.5.4" esquery "^1.0.0" - estraverse "^4.2.0" esutils "^2.0.2" file-entry-cache "^2.0.0" - glob "^7.0.3" - globals "^9.14.0" - ignore "^3.2.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.0.1" + ignore "^3.3.3" imurmurhash "^0.1.4" - inquirer "^0.12.0" - is-my-json-valid "^2.10.0" + inquirer "^3.0.6" is-resolvable "^1.0.0" - js-yaml "^3.5.1" - json-stable-stringify "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" - lodash "^4.0.0" - mkdirp "^0.5.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" natural-compare "^1.4.0" optionator "^0.8.2" - path-is-inside "^1.0.1" - pluralize "^1.2.1" - progress "^1.1.8" - require-uncached "^1.0.2" - shelljs "^0.7.5" - strip-bom "^3.0.0" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + regexpp "^1.0.1" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" strip-json-comments "~2.0.1" - table "^3.7.8" + table "4.0.2" text-table "~0.2.0" - user-home "^2.0.0" eslint@^5.0.1: version "5.13.0" @@ -2795,12 +2750,12 @@ eslint@^5.0.1: table "^5.0.2" text-table "^0.2.0" -espree@^3.4.0: - version "3.5.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" - integrity sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ== +espree@^3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" + integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== dependencies: - acorn "^5.2.1" + acorn "^5.5.0" acorn-jsx "^3.0.0" espree@^5.0.0: @@ -2859,7 +2814,7 @@ estraverse@^1.9.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" integrity sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q= -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= @@ -2874,14 +2829,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-emitter@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= - dependencies: - d "1" - es5-ext "~0.10.14" - event-stream@3.3.4, event-stream@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" @@ -2939,11 +2886,6 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -exit-hook@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" - integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= - expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -3051,6 +2993,15 @@ extend@^3.0.2, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +external-editor@^2.0.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== + dependencies: + chardet "^0.4.0" + iconv-lite "^0.4.17" + tmp "^0.0.33" + external-editor@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.0.tgz#dc35c48c6f98a30ca27a20e9687d7f3c77704bb6" @@ -3177,14 +3128,6 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -figures@^1.3.5: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" - integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= - dependencies: - escape-string-regexp "^1.0.5" - object-assign "^4.1.0" - figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -3712,7 +3655,7 @@ glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2: +glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== @@ -3761,16 +3704,16 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" +globals@^11.0.1: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + globals@^11.7.0: version "11.10.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.10.0.tgz#1e09776dffda5e01816b3bb4077c8b59c24eaa50" integrity sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ== -globals@^9.14.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" @@ -4393,7 +4336,7 @@ iconv-lite@0.4.23, iconv-lite@^0.4.22, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.4.24: +iconv-lite@^0.4.17, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -4417,12 +4360,7 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^3.2.0: - version "3.3.7" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" - integrity sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA== - -ignore@^3.3.5: +ignore@^3.3.3, ignore@^3.3.5: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== @@ -4493,28 +4431,29 @@ ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" integrity sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4= -innosetup-compiler@^5.5.60: - version "5.5.62" - resolved "https://registry.yarnpkg.com/innosetup-compiler/-/innosetup-compiler-5.5.62.tgz#fc12cd8d17cf75a2e3833b2754a5c2bc4f26cc4e" - integrity sha1-/BLNjRfPdaLjgzsnVKXCvE8mzE4= +innosetup@5.6.1: + version "5.6.1" + resolved "https://registry.yarnpkg.com/innosetup/-/innosetup-5.6.1.tgz#6e7031ba35b23e716e4f29686bc994052e0c278c" + integrity sha512-Eit24N3JR8O0Wpuq/dMWCl2r550eiNP2124SbdbwOob43x89WPGL/SGpZG5EPHu20kV2N+4TwvHwFIM8pFUJ0g== -inquirer@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" - integrity sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34= +inquirer@^3.0.6: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== dependencies: - ansi-escapes "^1.1.0" - ansi-regex "^2.0.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" cli-width "^2.0.0" - figures "^1.3.5" + external-editor "^2.0.4" + figures "^2.0.0" lodash "^4.3.0" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - string-width "^1.0.1" - strip-ansi "^3.0.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" through "^2.3.6" inquirer@^6.0.0: @@ -4560,11 +4499,6 @@ int64-buffer@^0.1.9: resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.9.tgz#9e039da043b24f78b196b283e04653ef5e990f61" integrity sha1-ngOdoEOyT3ixlrKD4EZT716ZD2E= -interpret@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0" - integrity sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA= - interpret@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" @@ -4762,7 +4696,7 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" -is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: +is-my-json-valid@^2.12.4: version "2.16.1" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" integrity sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ== @@ -5056,7 +4990,7 @@ js-yaml@3.6.1: argparse "^1.0.7" esprima "^2.6.0" -js-yaml@3.x, js-yaml@^3.5.1: +js-yaml@3.x: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" integrity sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA== @@ -5072,7 +5006,7 @@ js-yaml@^3.12.0: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.13.0: +js-yaml@^3.13.0, js-yaml@^3.9.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -5145,7 +5079,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: +json-stable-stringify@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= @@ -5439,7 +5373,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.3.0: +lodash@^4.13.1, lodash@^4.15.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= @@ -5965,11 +5899,6 @@ mute-stdout@^1.0.0: resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== -mute-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" - integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= - mute-stream@0.0.7, mute-stream@~0.0.4: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -6022,10 +5951,10 @@ native-is-elevated@^0.2.1: resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.2.1.tgz#70a2123a8575b9f624a3ef465d98cb74ae017385" integrity sha1-cKISOoV1ufYko+9GXZjLdK4Bc4U= -native-keymap@1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-1.2.5.tgz#1035a9417b9a9340cf8097763a43c76d588165a5" - integrity sha1-EDWpQXuak0DPgJd2OkPHbViBZaU= +native-keymap@1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-1.2.6.tgz#93d1b4c4ae0e9136bc14538cafe02c0bbe95bebf" + integrity sha512-8hEr6wNkb7OmGPFLFk1cAsnOt2Y3F4mtBffr8uOyX0kKOjr2JVetSt9TKjk0xyJw/B/HcEgMhXmjFKzGN+9JjA== native-watchdog@1.0.0: version "1.0.0" @@ -6384,11 +6313,6 @@ once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: dependencies: wrappy "1" -onetime@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= - onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -6847,10 +6771,10 @@ plugin-error@1.0.1, plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" -pluralize@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" - integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU= +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== portfinder@^1.0.13: version "1.0.20" @@ -7535,15 +7459,6 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readline2@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" - integrity sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - mute-stream "0.0.5" - rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -7591,6 +7506,11 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexpp@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" + integrity sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw== + regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -7769,7 +7689,7 @@ require-main-filename@^1.0.1: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= -require-uncached@^1.0.2: +require-uncached@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= @@ -7843,14 +7763,6 @@ resolve@^1.4.0: dependencies: path-parse "^1.0.6" -restore-cursor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" - integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= - dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -7905,13 +7817,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -run-async@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" - integrity sha1-yK1KXhEGYeQCp9IbUw4AnyX444k= - dependencies: - once "^1.3.0" - run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -7926,10 +7831,17 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rx-lite@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" - integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= rxjs@^6.1.0: version "6.2.2" @@ -8126,15 +8038,6 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shelljs@^0.7.5: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" - integrity sha1-3svPh0sNHl+3LhSxZKloMEjprLM= - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - sigmund@^1.0.1, sigmund@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" @@ -8181,10 +8084,12 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" - integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== + dependencies: + is-fullwidth-code-point "^2.0.0" slice-ansi@^2.0.0: version "2.1.0" @@ -8572,11 +8477,6 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -8665,17 +8565,17 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" -table@^3.7.8: - version "3.8.3" - resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" - integrity sha1-K7xULw/amGGnVdOUf+/Ys/UThV8= +table@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + integrity sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA== dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" table@^5.0.2: version "5.2.2" @@ -9279,13 +9179,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -user-home@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" - integrity sha1-nHC/2Babwdy/SGBODwS4tJzenp8= - dependencies: - os-homedir "^1.0.0" - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -9909,10 +9802,10 @@ xterm-addon-web-links@0.1.0-beta10: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta42: - version "3.15.0-beta42" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta42.tgz#8ed1f2928d46cb5f941dc39e4116782787a4b8a6" - integrity sha512-1hXcdnrhAsvmyTrcR+cz/B3O/Bv9oRRg4lPrOVoJkW+0otRfljJ5CHMfaL0uiZwphNoWpkHXrzApsY5GzS8o5Q== +xterm@3.15.0-beta50: + version "3.15.0-beta50" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta50.tgz#6413057fe36ff5808a41eba337f83076144d5c10" + integrity sha512-LAJ8kP3U8oXnVR3uaL4NxNFKcFDt3Zoec53hgYppwW8P5LdmL/dc0eDpgqiEsPLx4GgD37d8J92EcoMzKs2vVw== y18n@^3.2.1: version "3.2.1"